├── Crash-platform.png
├── README.md
├── Unity异常的类型.md
├── exception-common.png
├── symbol-path.png
└── symbol-retrace.png
/Crash-platform.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sundxing/Unity-Exception-Crash/c20f1cbbd9e37a4af6e1303b7ec5e4384d5094f3/Crash-platform.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Unity异常处理与分析
2 |
3 |
4 | ## 引言
5 | 对于程序开发者来说,异常可以理解为坏消息,为了避免异常我们需要良好的结构设计,完备的逻辑编写以及规范的代码格式。但是异常总会不和事宜的出现,针对可能异常的处理和异常的收集也至关重要。
6 |
7 | 此教程就针对异常处理,统计和分析进行展开讲解,
8 | 因为异常的内容知识非常庞大,特别是针对Android和iOS平台本身的Native异常,对于Native异常两个平台有大量的资料可供查阅学习,本教程给出了扩展链接,可以按需阅读。
9 |
10 | ## 核心要点
11 | - Unity exception的介绍和异常处理
12 | - Native crash的介绍
13 | - 上报平台Unity/Appcenter/Firebase/Bugly对比分析
14 | - 处理异常的一般原则和注意事项
15 | - 上报事所必要的信息
16 |
17 |
18 | ## Unity Excepction
19 | 默认情况下C#代码下Exception并不会造成进程杀死,这样的情况如果异常产生后会中断当前执行的代码逻辑,可能会造成一系列意外的结果。iOS平台上Unity提供了`Fast but no Exceptions` 选项[保证所有Exception触发Crash](https://docs.unity3d.com/Manual/iphone-iOS-Optimization.html)。对于Android平台,Unity并没有这样的选项,但是我们可以通过全局异常捕获接口获得Exception,然后强制终止/重启程序或者弹出异常弹窗让用户选择终止或重。下面我们会从种类、构建、处理三个方面介绍异常。
20 |
21 | ###异常种类
22 | Unity中Excepction都是派生于 `System.Exception`,下面是我们开发过程常见的Exception
23 | 
24 |
25 |
26 |
27 | 具体参阅:
28 |
29 | [Handling and throwing exceptions in .NET
30 | ](https://docs.microsoft.com/en-us/dotnet/standard/exceptions/)
31 |
32 | ### 构建异常
33 | 通过 `throw` 关键字可以抛出自定义Exception,三个构造方法可选择:
34 |
35 | ```
36 | public Exception();
37 | public Exception(string message);
38 | public Exception(string message, Exception innerException);
39 | ```
40 | 建议:
41 | 尽可能详尽的附加`message`信息
42 | `innerException`可以附加捕获的Exception,可用于保留异常第一现场的调用栈
43 |
44 |
45 | ### 异常处理
46 | #### 局部处理
47 | 使用try-catch-finally 语句。
48 |
49 | 如果使用了`Task`处理异步任务 ,还可以使用 `ContinueWith `获得Task运行期间的Exception,运行线程和Task相同。
50 |
51 | ```
52 | Task.Run(() => { throw new CustomException("task1 faulted.");})
53 | .ContinueWith( t => { Console.WriteLine("{0}: {1}",
54 | t.Exception.InnerException.GetType().Name,
55 | t.Exception.InnerException.Message);
56 | }, TaskContinuationOptions.OnlyOnFaulted);
57 | ```
58 | #### 全局处理
59 |
60 | Unity 本身提供了`LogCallback `监听程序Log和异常
61 |
62 | ``` c#
63 | ///
64 | /// Use this delegate type with Application.logMessageReceived or Application.logMessageReceivedThreaded to monitor what gets logged.
65 | ///
66 | public delegate void LogCallback(string condition, string stackTrace, LogType type);
67 | ```
68 | 其中 `LogType` 是
69 |
70 | ```
71 | public enum LogType
72 | {
73 | Error,
74 | Assert,
75 | Warning,
76 | Log,
77 | Exception,
78 | }
79 | ```
80 | 看到`LogType `中有 `Exception `,然后我们通过`Application.logMessageReceivedThreaded`注册接口,就可以获得C#代码中抛出的所有Exception了。
81 |
82 |
83 | ### Native Crash
84 | 前面我们介绍了Unity Exception,除了Unity托管代码本身产生的异常之外,运行平台也会有对应的异常,平台托管代码中产生的异常我们是Native Crash。
85 | 当然Native Crash也是跟随平台特性的不同也各有差异,具体到Android平台可以分为 `Java Exception` 和 `Android native crash` 两类,还有iOS平台的 `iOS native crash `
86 |
87 | #### **Android Java Exception**:
88 | 发生在Android 虚拟机层面的异常,我们通过Java全局的异常捕获类`Thread.UncaughtExceptionHandler`拿到各个线程抛出的Exception
89 | 例1-Java Exception
90 |
91 | ```
92 | java.lang.ArithmeticException: divide by zero
93 | at com.lbj.mvpflower.mvp.ui.activity.UserActivity.onUser(UserActivity.java:36)
94 | at java.lang.reflect.Method.invoke(Native Method)
95 | at android.view.View$DeclaredOnClickListener.onClick(View.java:4702)
96 | at android.view.View.performClick(View.java:5619)
97 | at android.view.View$PerformClick.run(View.java:22298)
98 | at android.os.Handler.handleCallback(Handler.java:754)
99 | at android.os.Handler.dispatchMessage(Handler.java:95)
100 | at android.os.Looper.loop(Looper.java:165)
101 | at android.app.ActivityThread.main(ActivityThread.java:6365)
102 | ```
103 | 更多详情:
104 |
105 | - [Android捕获最少知识](https://www.jianshu.com/p/c13eb158d39e)
106 |
107 | #### **Android Native Crash**:
108 |
109 | 简单理解Android的native crash就是发生在so库中异常,所以这些异常实际上就是Linux系统
110 | 常见导致Native Crash的原因有以下几种:
111 | 1. so库内部代码数组越界、缓冲区溢出、空指针、野指针等;
112 | 2. Android ART发现或出现异常;
113 | 3. 其他framework、Kernel或硬件bug;
114 |
115 |
116 | 例2-Android Crash
117 |
118 | ```
119 | E/CRASH (32251): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 745a8008
120 | E/CRASH (32251): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
121 | E/CRASH (32251): Build type 'Release', Scripting Backend 'il2cpp', CPU 'armeabi-v7a'
122 | E/CRASH (32251): Build fingerprint: 'Lenovo/A7600-F/A7600-F:4.4.2/KOT49H/A7600F_A442_000_027_141207_ROW:user/release-keys'
123 | E/CRASH (32251): Revision: '0'
124 | E/CRASH (32251): pid: 32251, tid: 10190, name: Flurry #82 >>> my.package.demo <<<
125 | E/CRASH (32251): r0 77878008 r1 745a8008 r2 0000ac50 r3 00320057
126 | E/CRASH (32251): r4 00310035 r5 00470062 r6 00730077 r7 006d0049
127 | E/CRASH (32251): r8 00300068 r9 00000000 sl 6999eb88 fp 0002bc70
128 | E/CRASH (32251): ip 00480064 sp 793f7a80 lr 007a0042 pc 400bd480 cpsr 793f7790
129 | E/CRASH (32251):
130 | E/CRASH (32251): backtrace:
131 | E/CRASH (32251): #00 pc 00026480 /system/lib/libc.so (__memcpy_base_aligned+52)
132 | E/CRASH (32251): #01 pc 00020bf9 /system/lib/libbinder.so (android::Parcel::appendFrom(android::Parcel const*, unsigned int, unsigned int)+136)
133 | E/CRASH (32251): #02 pc 0006cb9b /system/lib/libandroid_runtime.so
134 | E/CRASH (32251): #03 pc 0001e90c /system/lib/libdvm.so (dvmPlatformInvoke+112)
135 | E/CRASH (32251): #04 pc 0004fbbd /system/lib/libdvm.so (dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*)+484)
136 | E/CRASH (32251): #05 pc 00027ce8 /system/lib/libdvm.so
137 | E/CRASH (32251): #06 pc 0002f2f0 /system/lib/libdvm.so (dvmMterpStd(Thread*)+76)
138 | E/CRASH (32251): #07 pc 0002c7d4 /system/lib/libdvm.so (dvmInterpret(Thread*, Method const*, JValue*)+188)
139 | E/CRASH (32251): #08 pc 00062ef9 /system/lib/libdvm.so (dvmCallMethodV(Thread*, Method const*, Object*, bool, JValue*, std::__va_list)+340)
140 | E/CRASH (32251): #09 pc 00062f1d /system/lib/libdvm.so (dvmCallMethod(Thread*, Method const*, Object*, JValue*, ...)+20)
141 | E/CRASH (32251): #10 pc 000575c5 /system/lib/libdvm.so
142 | ```
143 |
144 | - 信号名:signal 11 (SIGSEGV)
145 | - 信号产生的原因:code 1 (SEGV_MAPERR)
146 | - 异常的内存地址:fault addr 745a8008(如果是SIGSEGV/SIGBUS等信号,一般都会有异常内存地址显示)
147 | - 编译类型:il2cpp
148 | - ABI信息:armeabi-v7a
149 | - 设备型号:Lenovo
150 | - Android系统版本:4.4.2
151 | - 系统的build号:A7600FA442000027141207_ROW
152 | - 系统的类型:user(对应有user/eng/userdebug/optional等,user表示是release版本,eng是调试版本)
153 | - Crash的进程号:32251
154 | - Crash的线程号:10190
155 | - Crash的线程名称:Flurry #82(名称可能被裁减导致不全)
156 | - Crash的进程名称:my.package.demo
157 | - 中止消息:无(如果是SIGABRT,可能有类似javavmext.cc:504] JNI DETECTED ERROR IN APPLICATION: java_array == null这样的消息)
158 | - 下面是 寄存器指向的内存地址:
159 | > r0 77878008 r1 745a8008 r2 0000ac50 r3 00320057 ...
160 |
161 | - 最后Native调用栈(栈帧序号-pc地址值-虚拟内存映射区域名称-(对应符号-符号偏移量):
162 | > #00 pc 00026480 /system/lib/libc.so (__memcpy_base_aligned+52)
163 | > ...
164 |
165 | 更多详情:
166 | - [如何分析Android Native Crash](https://cloud.tencent.com/developer/article/1192001)
167 |
168 | #### **iOS Native Crash**
169 |
170 |
171 | 例3-iOS Crash
172 |
173 | ```
174 | Incident Identifier: D9857968-7AF7-40C0-BA05-192EF275F380
175 | Hardware Model: iPhone11,8
176 | Process: demo [4801]
177 | Path: /private/var/containers/Bundle/Application/03D4FE4F-26BC-47E5-A1D9-B8FF1D379B4A/demo.app/demo
178 | Identifier: my.bundleid.demo
179 | Version: 2 (1.0)
180 | AppStoreTools: 11E608a
181 | AppVariant: 1:iPhone11,8:13
182 | Beta: YES
183 | Code Type: ARM-64 (Native)
184 | Role: Foreground
185 | Parent Process: launchd [1]
186 | Coalition: my.bundleid.demo [1515]
187 |
188 |
189 | Date/Time: 2020-07-13 08:28:25.9201 +0700
190 | Launch Time: 2020-07-13 08:27:57.4775 +0700
191 | OS Version: iPhone OS 13.3 (17C54)
192 | Release Type: User
193 | Baseband Version: 2.03.07
194 | Report Version: 104
195 |
196 | Exception Type: EXC_BAD_ACCESS (SIGBUS)
197 | Exception Subtype: KERN_PROTECTION_FAILURE at 0x000000011b747ff0
198 | VM Region Info: 0x11b747ff0 is in 0x11b734000-0x11b748000; bytes after start: 81904 bytes before end: 15
199 | REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
200 | MALLOC_LARGE 000000011b730000-000000011b734000 [ 16K] rw-/rwx SM=PRV
201 | ---> CG raster data 000000011b734000-000000011b748000 [ 80K] r--/r-- SM=PRV
202 | MALLOC_LARGE 000000011b748000-000000011b74c000 [ 16K] rw-/rwx SM=PRV
203 |
204 | Termination Signal: Bus error: 10
205 | Termination Reason: Namespace SIGNAL, Code 0xa
206 | Terminating Process: exc handler [4801]
207 | Triggered by Thread: 38
208 |
209 | Thread 0 name:
210 | Thread 0:
211 | 0 libsystem_kernel.dylib 0x00000001b92c8c04 mach_msg_trap + 8
212 | 1 libsystem_kernel.dylib 0x00000001b92c8020 mach_msg + 76 (mach_msg.c:103)
213 | 2 CoreFoundation 0x00000001b947b964 __CFRunLoopServiceMachPort + 220 (CFRunLoop.c:2575)
214 | 3 CoreFoundation 0x00000001b94767fc __CFRunLoopRun + 1428 (CFRunLoop.c:2931)
215 | 4 CoreFoundation 0x00000001b9475f40 CFRunLoopRunSpecific + 480 (CFRunLoop.c:3192)
216 | 5 GraphicsServices 0x00000001c36f3534 GSEventRunModal + 108 (GSEvent.c:2246)
217 | 6 UIKitCore 0x00000001bd5eea60 UIApplicationMain + 1940 (UIApplication.m:4773)
218 | 7 UnityFramework 0x000000010281a330 -[UnityFramework runUIApplicationMainWithArgc:argv:] + 108 (main.mm:96)
219 | 8 demo 0x0000000100eb7e1c main + 68 (main.mm:26)
220 | 9 libdyld.dylib 0x00000001b92f4e18 start + 4
221 |
222 | Thread 1:
223 | 0 libsystem_pthread.dylib 0x00000001b92119e0 start_wqthread + 0
224 |
225 | Thread 2:
226 | 0 libsystem_pthread.dylib 0x00000001b92119e0 start_wqthread + 0
227 |
228 | ····
229 | Thread 38 Crashed:
230 | 0 libsystem_platform.dylib 0x00000001b9206688 _platform_memmove + 408
231 | 1 UnityFramework 0x000000010334f840 il2cpp::icalls::mscorlib::System::Buffer::BlockCopyInternal(Il2CppArray*, int, Il2CppArray*, int, int) + 176
232 | 2 UnityFramework 0x0000000103aaee4c StreamWriter_Write_m8056BDE8A4AD4816F9D7DBDBCB80D03BE8F3ED14 + 192 (mscorlib9.cpp:21738)
233 | 3 UnityFramework 0x0000000103ab22f4 UnexceptionalStreamWriter_Write_mAC310C8D24F673608DC7F130666E572ADD388E07 + 92 (mscorlib9.cpp:24996)
234 | 4 UnityFramework 0x0000000103729968 Logger_Log_mC3BD9B8C40067382718BD9A54688A92599404E6B + 240 (SmartFox2X2.cpp:32959)
235 | 5 UnityFramework 0x00000001036f8548 UDPManager_OnUDPData_mCD9304C4A4BD03D6F85EFAD9512893D4B2D037DC + 396 (SmartFox2X1.cpp:19687)
236 | 6 UnityFramework 0x0000000103712ec0 OnDataDelegate_Invoke_mF1F76E2DCD9F8028B8956AE76035840B21615920 + 488 (SmartFox2X1.cpp:0)
237 | 7 UnityFramework 0x00000001037175fc ThreadManager_InThread_m70E3D0A1EBE6F3AF67DFE1E8D50F54463DA49D10 + 180 (SmartFox2X2.cpp:18993)
238 | 8 UnityFramework 0x00000001039fcb98 ThreadStart_Invoke_m11B6A66E82F02C74399A7314C14C7F52393CC4B4 + 284 (ClassInlines.h:0)
239 | 9 UnityFramework 0x00000001039d0940 ContextCallback_Invoke_m76E65E7A67AA99858554F451E0F78E22B6478998 + 440 (ClassInlines.h:0)
240 | 10 UnityFramework 0x00000001039d1bf8 ExecutionContext_RunInternal_m5BF955CE8B04D9A7C959B9011579CC0FABD5FC56 + 316 (mscorlib19.cpp:30738)
241 | 11 UnityFramework 0x00000001039fcb98 ThreadStart_Invoke_m11B6A66E82F02C74399A7314C14C7F52393CC4B4 + 284 (ClassInlines.h:0)
242 | 12 UnityFramework 0x000000010286b620 RuntimeInvoker_TrueVoid_t22962CB4C05B1D89B55A6E1139F0E87A90987017(void (*)(), MethodInfo const*, void*, void**) + 20 (Il2CppInvokerTable.cpp:18279)
243 | 13 UnityFramework 0x00000001033925c8 il2cpp::vm::Runtime::Invoke(MethodInfo const*, void*, void**, Il2CppException**) + 116
244 | 14 UnityFramework 0x000000010335c678 il2cpp::icalls::mscorlib::System::Threading::ThreadStart(void*) + 108
245 | 15 UnityFramework 0x0000000103365a7c il2cpp::os::Thread::RunWrapper(void*) + 88
246 | 16 UnityFramework 0x0000000103367d14 il2cpp::os::ThreadImpl::ThreadStartWrapper(void*) + 40
247 | 17 libsystem_pthread.dylib 0x00000001b9209840 _pthread_start + 168 (pthread.c:896)
248 | 18 libsystem_pthread.dylib 0x00000001b92119f4 thread_start + 8
249 |
250 | ····
251 |
252 | Binary Images:
253 | 0x100eb0000 - 0x100eb7fff demo arm64 <14cde28b305d3bffb8abdfab5a30df25> /var/containers/Bundle/Application/03D4FE4F-26BC-47E5-A1D9-B8FF1D379B4A/demo.app/demo
254 | 0x100f80000 - 0x100f8bfff libobjc-trampolines.dylib arm64e <028df612175838e498958fd8c6a9a03c> /usr/lib/libobjc-trampolines.dylib
255 |
256 | ```
257 | 上面省略了其他线程的信息
258 |
259 | - Incident Identifier : Crash唯一ID
260 | - Hardware Model: 设备型号
261 | - Process: 可执行文件名
262 | - Identifier :Bundle ID
263 | - Code Type : CPU架构
264 | - OS Version: 系统版本号号
265 | - Report Version :崩溃日志版本号
266 | - Exception Type - Termination Reason: 是异常的类型和原因
267 | - Triggered by Thread: 38 : 引发的线程
268 | - Thread 0: 主线程
269 | - 崩溃发生的堆栈 (位置, 地址, 函数符号+ 偏移量):
270 | > 0 libsystem_kernel.dylib 0x00000001b92c8c04 mach_msg_trap + 8
271 |
272 | - Binary Images: 二进制文件内存信息 ( 地址起始-地址结束 名称 CPU架构 路径)
273 | > 0x100eb0000 - 0x100eb7fff demo arm64 <14cde28b305d3bffb8abdfab5a30df25> /var/containers/Bundle/Application/03D4FE4F-26BC-47E5-A1D9-B8FF1D379B4A/demo.app/demo
274 |
275 | 更多详情:
276 |
277 | - [iOS Crash日志结构介绍](https://jianli2017.top/wiki/IOS/crash/1_system_Crash_Type/)
278 | - [了解和分析iOS Crash Report](https://juejin.im/post/6844903774780145678)
279 |
280 |
281 |
282 | 对于Debug环境中我们拿到的Crash日志通常都是带有符号的,也就是我们可以追踪到具体的crash方法和行号。如果Crash堆栈没有解析符号表,这种情况就需要我们手动解析符号表了,当然很多Crash平台都集成了解析符号表的功能,我们只需要自己上传即可。下面我们就介绍下符号表相关的知识。
283 |
284 | ### 解析符号表
285 | 为了能快速并准确地定位用户APP发生Crash的代码位置,我们使用符号表对APP发生Crash的程序堆栈进行解析和还原, 比如一个iOS的crash堆栈:
286 | 
287 | ##### 符号表
288 | 简单来说,符号表是内存地址与函数名,文件名,行号的映射表
289 | > <起始地址> <结束地址> <函数> [<文件名:行号>]
290 |
291 |
292 | - 对于Android工程,一般情况下我们采用 il2cpp 方式构建,unity-2018.4会把符号表自动打包成一个压缩包文件。后缀 `.symbols.zip`, 存放位置和 `.apk` 文件在同一目录。
293 | 
294 |
295 | - iOS系统的下的符号表就是dSYM文件,文件名通常为:xxx.app.dSYM。我们使用xcode编译完项目后,会生产dSYM文件,和app文件在同一个目录下
296 |
297 | 符号表相关的知识参考:
298 | Bugly官方手册 - [Android符号表](https://bugly.qq.com/docs/user-guide/symbol-configuration-android/?v=20200622202242),[iOS符号表](https://bugly.qq.com/docs/user-guide/symbol-configuration-ios/?v=20200622202242)
299 |
300 | ##### 解析符号表
301 | 拿到符号表,并且确认**UIID一致**的情况下,我们就可以进行工具进行堆栈符号解析了。
302 |
303 | - Android平台下可以使用NDK库中 `add2line ` `ndk-stack` 工具,`ndk-stack`可以对Android的Crash-Log进行提取和解析,非常方便在拿到Crashlog的情况下分析。
304 | 我们通过以下命名可以获取实时的Crash信息
305 | > adb shell logcat | ndk-stack -sym $PROJECT_PATH/obj/local/armeabi
306 | 更多内容请翻阅:
307 | [Android Native crash日志分析](https://www.cnblogs.com/willhua/p/6718379.html)
308 |
309 | - iOS平台下两种工具`symbolicatecrash ` 和`atos ` ,`symbolicatecrash`其实就是对`atos`的封装
310 | 更多内容请翻阅:
311 | [OS Crash分析必备:符号化系统库方法](https://zuikyo.github.io/2016/12/18/iOS%20Crash%E6%97%A5%E5%BF%97%E5%88%86%E6%9E%90%E5%BF%85%E5%A4%87%EF%BC%9A%E7%AC%A6%E5%8F%B7%E5%8C%96%E7%B3%BB%E7%BB%9F%E5%BA%93%E6%96%B9%E6%B3%95/)
312 |
313 |
314 |
315 | ## Unity上报平台的选择
316 |
317 | ### 主流上报平台的对比分析
318 | Unity平台的异常上报通常有四家较为主流的平台,
319 |
320 | - [Bugly](https://bugly.qq.com/docs/user-guide/instruction-manual-plugin-unity/?v=20200312155538)
321 | - [Appcenter](https://docs.microsoft.com/en-gb/appcenter/sdk/crashes/unity)
322 | - [Unity](https://unitytech.github.io/clouddiagnostics/)
323 | - [Firebase](https://firebase.google.com/docs/crashlytics/get-started?authuser=0&platform=unity)
324 |
325 | 这里对各个平台的特点梳理了一个表格,集成后可以参阅(标绿的部分是明显优势项,表格中仅拿Android项目进行了测试统计):
326 | 
327 |
328 | 根据咱们公司风控要求,**Bugly限制国内平台使用**,**Firebase限制全平台使用**,所以产品的集成建议以下组合方式:
329 | > 国内产品推荐:Bugly, Unity
330 | >
331 | > 海外产品推荐:Appcenter, Unity
332 |
333 |
334 | ### 搭建自己的上报平台
335 | 前面介绍了第三方Crash或者Unity平台,假如我们需要上报到自己的平台,需要分几个方面进行处理,这些可以单独进行集成:
336 | 1. Exception 捕获
337 | 2. Native Crash, 包括Android和iOS平台
338 |
339 | #### 全局异常处理
340 | 前面已经讲过 `Application.logMessageReceivedThreaded`注册接口可以获取全局的Exception,所以Unity托段代码的异常获得还是非常简单的,实现对应的接口方法即可监听。
341 |
342 | #### iOS 异常
343 | iOS平台提供了捕获异常的接口:
344 |
345 | ```
346 | /* Set the uncaught exception handler */
347 | NSSetUncaughtExceptionHandler(&uncaught_exception_handler);
348 | ```
349 | 如果同时使用了注册了第三方的Crash平台, 需要先保存第三方的 ExceptionHandler,然后在设置自己处理 exception 的 ExceptionHandler,在自己的 ExceptionHandler 处理完异常之后,再将异常交给第三方处理。
350 |
351 | 如果需要集成服务端解析符号表,请参考:
352 | [有赞 crash 平台符号化实践](https://www.infoq.cn/article/JLtuf3PGfJV6ovjzu1sq?utm_source=related_read_bottom&utm_medium=article)
353 |
354 | #### Android Java 异常
355 | Android平台也提供了处理Java异常的全局接口 `Thread.UncaughtExceptionHandler`, 我们实现此接口通过
356 | `Thread.setDefaultUncaughtExceptionHandler` 注册给系统即可。同样iOS平台类似,应为此接口值接受一个Handler,需要先通过 `Thread.getDefaultUncaughtExceptionHandler` 获取之前的Handler,再根据需要将捕获的Exception进行转交。
357 |
358 | #### Android Native 异常
359 | Android 平台可以使用 [google breakpad](https://github.com/google/breakpad) 捕获native crash(TODO: 集成方案:Sentry), Appcenter平台就是采用的该方案。
360 |
361 |
362 | ## 异常处理和上报
363 |
364 | 当我们了解了所有的异常类型,也选择了合适的上报平台,最后就可以进行异常上报了。通常第三方都会主动收集异常信息,`AppCenter`这样的平台甚至会主动上报Error的log, `Bugly`的Android平台还是收集 logcat日志信息。 除了平台做的这些事情我们还需要注意两件非常重要的事情,来快速定位Crash:
365 |
366 | 1. 恰当的处理异常:异常的信息能够发现具体的**异常原因** 和 **异常发生位置**
367 | 2. 上报异常之外的其他信息,进行辅助判断,因为很多的native异常位置指向性并不能定位原因。
368 |
369 | #### 恰当的处理异常
370 |
371 | ##### 正确使用try-catch-finially语句处理异常。
372 |
373 | - `try`的作用域尽量单一,方便错误定位。
374 | - `catch`中做恢复资源,如果不能正确处理请上报事件或debug下输出错误,最后或者是重新throw。
375 | - `finally`中清理资源,可以不`catch` Exception,仅使用try-finally做资源闭环处理。
376 |
377 | 另外更多使用规则请参阅
378 | [Best practices for exceptions](https://docs.microsoft.com/en-us/dotnet/standard/exceptions/best-practices-for-exceptions)
379 |
380 | ##### throw 关键字使用
381 |
382 |
383 | * `throw [e];` 抛出一个异常,调用栈是当前调用方法。
384 |
385 | 注意: 当try-catch中 抛出一个新的异常,捕获的异常的调用栈并不是真正错误来源。如果需要保留catch的Exception,需要使用下面的构建方法:
386 | > `public Exception(string message, Exception innerException);`
387 |
388 | 一个常见的用法:
389 |
390 | ```c#
391 | public class ProcessFile
392 | {
393 | public static void Main()
394 | {
395 | FileStream fs;
396 | try
397 | {
398 | fs = new FileStream("data.txt", FileMode.Open);
399 | var sr = new StreamReader(fs);
400 | // ....
401 | }
402 | catch(FileNotFoundException e)
403 | {
404 | Console.WriteLine($"[Data File Missing] {e}");
405 | throw new FileNotFoundException(@"[data.txt not in c:\temp directory]", e);
406 | }
407 | finally
408 | {
409 | if (fs != null)
410 | fs.Close();
411 | }
412 | }
413 | }
414 | ```
415 |
416 | * `throw;` 重新抛出try-catch的异常 ,通常可作为代码异常的监听,但不做拦截
417 |
418 | ##### 方便确定异常发生的位置
419 | il2cpp目前并不记录异常代码的行号,这对定位产生了一定的困难。以下几个点可以让我们更加方便确定异常的位置:
420 |
421 | - 编写职责单一的方法,方法行数不易过多。
422 | - 异常捕获时提供额外信息。
423 | - 准确记录堆栈路径,通常是自己构建Exception时注意保留Catch的Exception堆栈。
424 |
425 |
426 | #### 上报关键信息
427 |
428 | 我们可以通过反馈入口或者异常捕获拿拿到线上用户的现场信息,为了方便定位Crash,应当尽量提供一下现场信息
429 |
430 | - 设备信息: 包括设备基本类型,系统版本,系统语言,定义的UIID, 内存情况,存储情况(需要单独实现Native接口)等
431 |
432 | ```
433 | SystemInfo.deviceModel,
434 | SystemInfo.deviceName,
435 | SystemInfo.deviceType,
436 | SystemInfo.deviceUniqueIdentifier,
437 | SystemInfo.operatingSystem,
438 | Localization.language,
439 |
440 | SystemInfo.systemMemorySize,
441 | SystemInfo.processorCount,
442 | SystemInfo.processorType,
443 | Screen.currentResolution.width,
444 | Screen.currentResolution.height,
445 | Screen.dpi,
446 | Screen.fullScreen,
447 |
448 | SystemInfo.graphicsDeviceName,
449 | SystemInfo.graphicsDeviceVendor,
450 | SystemInfo.graphicsMemorySize,
451 | SystemInfo.graphicsPixelFillrate,
452 | SystemInfo.maxTextureSize,
453 | ```
454 |
455 | - 应用信息:运行时间,异常时所在界面,用户操作的动作链等
456 | - Trace事件:为了方便业务分析,我们在开发过程会上报事件用户埋点分析,除此之外我们可以提供Trace事件类型,主要来统计模块加载和错误输出,发生异常是可以根据场景附加上多个事件集合。
457 | - 日志文件:在可能的情况下,附加当前应用的日志文件,AppCenter提供了附加文件的接口,我们可以将日志文件一并上传。
458 | - 反馈接口:用户提供额外的文字描述和截屏。
459 |
460 |
--------------------------------------------------------------------------------
/Unity异常的类型.md:
--------------------------------------------------------------------------------
1 | # Unity异常处理与分析
2 |
3 |
4 | ## 引言
5 | 对于程序开发者来说,异常可以理解为坏消息,为了避免异常我们需要良好的结构设计,完备的逻辑编写以及规范的代码格式。但是异常总会不和事宜的出现,针对可能异常的处理和异常的收集也至关重要。
6 |
7 | 此教程就针对异常处理,统计和分析进行展开讲解,
8 | 因为异常的内容知识非常庞大,特别是针对Android和iOS平台本身的Native异常,对于Native异常两个平台有大量的资料可供查阅学习,本教程给出了扩展链接,可以按需阅读。
9 |
10 | ## 核心要点
11 | - Unity exception的介绍和异常处理
12 | - Native crash的介绍
13 | - 上报平台Unity/Appcenter/Firebase/Bugly对比分析
14 | - 处理异常的一般原则和注意事项
15 | - 上报事所必要的信息
16 |
17 |
18 | ## Unity Excepction
19 | 默认情况下C#代码下Exception并不会造成进程杀死,这样的情况如果异常产生后会中断当前执行的代码逻辑,可能会造成一系列意外的结果。iOS平台上Unity提供了`Fast but no Exceptions` 选项[保证所有Exception触发Crash](https://docs.unity3d.com/Manual/iphone-iOS-Optimization.html)。对于Android平台,Unity并没有这样的选项,但是我们可以通过全局异常捕获接口获得Exception,然后强制终止/重启程序或者弹出异常弹窗让用户选择终止或重。下面我们会从种类、构建、处理三个方面介绍异常。
20 |
21 | ###异常种类
22 | Unity中Excepction都是派生于 `System.Exception`,下面是我们开发过程常见的Exception
23 | 
24 |
25 |
26 |
27 | 具体参阅:
28 |
29 | [Handling and throwing exceptions in .NET
30 | ](https://docs.microsoft.com/en-us/dotnet/standard/exceptions/)
31 |
32 | ### 构建异常
33 | 通过 `throw` 关键字可以抛出自定义Exception,三个构造方法可选择:
34 |
35 | ```
36 | public Exception();
37 | public Exception(string message);
38 | public Exception(string message, Exception innerException);
39 | ```
40 | 建议:
41 | 尽可能详尽的附加`message`信息
42 | `innerException`可以附加捕获的Exception,可用于保留异常第一现场的调用栈
43 |
44 |
45 | ### 异常处理
46 | #### 局部处理
47 | ##### 使用try-catch-finally 语句
48 |
49 | 注意:**如果try作用域包含了异步处理,该异步处理产生的异常不会被捕获**
50 | 常见异步情况有:
51 |
52 | - 协程(Coroutines)。 如果想要协程处理异常可以参考 [Catching Exceptions in Coroutines](https://www.jacksondunstan.com/articles/3718)
53 | - 线程处理
54 | - 其他延时操作,比如 `InvokeDelayed` 调用
55 |
56 | ##### 使用ContinueWith
57 | 如果使用了`Task`处理异步任务 ,还可以使用 `ContinueWith `获得Task运行期间的Exception,运行线程和Task相同。
58 |
59 | ```
60 | Task.Run(() => { throw new CustomException("task1 faulted.");})
61 | .ContinueWith( t => { Console.WriteLine("{0}: {1}",
62 | t.Exception.InnerException.GetType().Name,
63 | t.Exception.InnerException.Message);
64 | }, TaskContinuationOptions.OnlyOnFaulted);
65 | ```
66 |
67 |
68 | #### 全局处理
69 |
70 | Unity 本身提供了`LogCallback `监听程序Log和异常
71 |
72 | ``` c#
73 | ///
74 | /// Use this delegate type with Application.logMessageReceived or Application.logMessageReceivedThreaded to monitor what gets logged.
75 | ///
76 | public delegate void LogCallback(string condition, string stackTrace, LogType type);
77 | ```
78 | 其中 `LogType` 是
79 |
80 | ```
81 | public enum LogType
82 | {
83 | Error,
84 | Assert,
85 | Warning,
86 | Log,
87 | Exception,
88 | }
89 | ```
90 | 看到`LogType `中有 `Exception `,然后我们通过`Application.logMessageReceivedThreaded`注册接口,就可以获得C#代码中未被捕获的的所有Exception了,需要注意的是`LogCallback`接口拿不到Exception实例,只有Excetipion信息和调用栈。
91 |
92 |
93 | ### Native Crash
94 | 前面我们介绍了Unity Exception,除了Unity托管代码本身产生的异常之外,运行平台也会有对应的异常,平台托管代码中产生的异常我们是Native Crash,另外这些异常错误直接造成App终止运行,也就是我们常说的Crash闪退了。
95 | 当然Native Crash也是跟随平台特性的不同也各有差异,具体到Android平台可以分为 `Java Exception` 和 `Android native crash` 两类,还有iOS平台的 `iOS native crash `
96 |
97 | #### **Android Java Exception**:
98 | 发生在Android 虚拟机层面的异常,我们通过Java全局的异常捕获类`Thread.UncaughtExceptionHandler`拿到各个线程抛出的Exception。
99 |
100 | 例1-Java Exception
101 |
102 | ```
103 | java.lang.ArithmeticException: divide by zero
104 | at com.lbj.mvpflower.mvp.ui.activity.UserActivity.onUser(UserActivity.java:36)
105 | at java.lang.reflect.Method.invoke(Native Method)
106 | at android.view.View$DeclaredOnClickListener.onClick(View.java:4702)
107 | at android.view.View.performClick(View.java:5619)
108 | at android.view.View$PerformClick.run(View.java:22298)
109 | at android.os.Handler.handleCallback(Handler.java:754)
110 | at android.os.Handler.dispatchMessage(Handler.java:95)
111 | at android.os.Looper.loop(Looper.java:165)
112 | at android.app.ActivityThread.main(ActivityThread.java:6365)
113 | ```
114 | 更多详情:
115 |
116 | - [Android捕获最少知识](https://www.jianshu.com/p/c13eb158d39e)
117 |
118 | #### **Android Native Crash**:
119 |
120 | 简单理解Android的native crash就是发生在so库中异常,所以这些异常实际上就是Linux系统
121 | 常见导致Native Crash的原因有以下几种:
122 |
123 | 1. so库内部代码数组越界、缓冲区溢出、空指针、野指针等;
124 | 2. Android ART发现或出现异常;
125 | 3. 其他framework、Kernel或硬件bug;
126 |
127 |
128 | 例2-Android Crash
129 |
130 | ```
131 | E/CRASH (32251): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 745a8008
132 | E/CRASH (32251): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
133 | E/CRASH (32251): Build type 'Release', Scripting Backend 'il2cpp', CPU 'armeabi-v7a'
134 | E/CRASH (32251): Build fingerprint: 'Lenovo/A7600-F/A7600-F:4.4.2/KOT49H/A7600F_A442_000_027_141207_ROW:user/release-keys'
135 | E/CRASH (32251): Revision: '0'
136 | E/CRASH (32251): pid: 32251, tid: 10190, name: Flurry #82 >>> my.package.demo <<<
137 | E/CRASH (32251): r0 77878008 r1 745a8008 r2 0000ac50 r3 00320057
138 | E/CRASH (32251): r4 00310035 r5 00470062 r6 00730077 r7 006d0049
139 | E/CRASH (32251): r8 00300068 r9 00000000 sl 6999eb88 fp 0002bc70
140 | E/CRASH (32251): ip 00480064 sp 793f7a80 lr 007a0042 pc 400bd480 cpsr 793f7790
141 | E/CRASH (32251):
142 | E/CRASH (32251): backtrace:
143 | E/CRASH (32251): #00 pc 00026480 /system/lib/libc.so (__memcpy_base_aligned+52)
144 | E/CRASH (32251): #01 pc 00020bf9 /system/lib/libbinder.so (android::Parcel::appendFrom(android::Parcel const*, unsigned int, unsigned int)+136)
145 | E/CRASH (32251): #02 pc 0006cb9b /system/lib/libandroid_runtime.so
146 | E/CRASH (32251): #03 pc 0001e90c /system/lib/libdvm.so (dvmPlatformInvoke+112)
147 | E/CRASH (32251): #04 pc 0004fbbd /system/lib/libdvm.so (dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*)+484)
148 | E/CRASH (32251): #05 pc 00027ce8 /system/lib/libdvm.so
149 | E/CRASH (32251): #06 pc 0002f2f0 /system/lib/libdvm.so (dvmMterpStd(Thread*)+76)
150 | E/CRASH (32251): #07 pc 0002c7d4 /system/lib/libdvm.so (dvmInterpret(Thread*, Method const*, JValue*)+188)
151 | E/CRASH (32251): #08 pc 00062ef9 /system/lib/libdvm.so (dvmCallMethodV(Thread*, Method const*, Object*, bool, JValue*, std::__va_list)+340)
152 | E/CRASH (32251): #09 pc 00062f1d /system/lib/libdvm.so (dvmCallMethod(Thread*, Method const*, Object*, JValue*, ...)+20)
153 | E/CRASH (32251): #10 pc 000575c5 /system/lib/libdvm.so
154 | ```
155 |
156 | - 信号名:signal 11 (SIGSEGV)
157 | - 信号产生的原因:code 1 (SEGV_MAPERR)
158 | - 异常的内存地址:fault addr 745a8008(如果是SIGSEGV/SIGBUS等信号,一般都会有异常内存地址显示)
159 | - 编译类型:il2cpp
160 | - ABI信息:armeabi-v7a
161 | - 设备型号:Lenovo
162 | - Android系统版本:4.4.2
163 | - 系统的build号:A7600FA442000027141207_ROW
164 | - 系统的类型:user(对应有user/eng/userdebug/optional等,user表示是release版本,eng是调试版本)
165 | - Crash的进程号:32251
166 | - Crash的线程号:10190
167 | - Crash的线程名称:Flurry #82(名称可能被裁减导致不全)
168 | - Crash的进程名称:my.package.demo
169 | - 中止消息:无(如果是SIGABRT,可能有类似javavmext.cc:504] JNI DETECTED ERROR IN APPLICATION: java_array == null这样的消息)
170 | - 下面是 寄存器指向的内存地址:
171 | > r0 77878008 r1 745a8008 r2 0000ac50 r3 00320057 ...
172 |
173 | - 最后Native调用栈(栈帧序号-pc地址值-虚拟内存映射区域名称-(对应符号-符号偏移量):
174 | > #00 pc 00026480 /system/lib/libc.so (__memcpy_base_aligned+52)
175 | > ...
176 |
177 | 更多详情:
178 | - [如何分析Android Native Crash](https://cloud.tencent.com/developer/article/1192001)
179 |
180 | #### **iOS Native Crash**
181 |
182 | 与Android类似,iOS可以拿到的Crash记录也非常详细
183 |
184 | 例3-iOS Crash
185 |
186 | ```
187 | Incident Identifier: D9857968-7AF7-40C0-BA05-192EF275F380
188 | Hardware Model: iPhone11,8
189 | Process: demo [4801]
190 | Path: /private/var/containers/Bundle/Application/03D4FE4F-26BC-47E5-A1D9-B8FF1D379B4A/demo.app/demo
191 | Identifier: my.bundleid.demo
192 | Version: 2 (1.0)
193 | AppStoreTools: 11E608a
194 | AppVariant: 1:iPhone11,8:13
195 | Beta: YES
196 | Code Type: ARM-64 (Native)
197 | Role: Foreground
198 | Parent Process: launchd [1]
199 | Coalition: my.bundleid.demo [1515]
200 |
201 |
202 | Date/Time: 2020-07-13 08:28:25.9201 +0700
203 | Launch Time: 2020-07-13 08:27:57.4775 +0700
204 | OS Version: iPhone OS 13.3 (17C54)
205 | Release Type: User
206 | Baseband Version: 2.03.07
207 | Report Version: 104
208 |
209 | Exception Type: EXC_BAD_ACCESS (SIGBUS)
210 | Exception Subtype: KERN_PROTECTION_FAILURE at 0x000000011b747ff0
211 | VM Region Info: 0x11b747ff0 is in 0x11b734000-0x11b748000; bytes after start: 81904 bytes before end: 15
212 | REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
213 | MALLOC_LARGE 000000011b730000-000000011b734000 [ 16K] rw-/rwx SM=PRV
214 | ---> CG raster data 000000011b734000-000000011b748000 [ 80K] r--/r-- SM=PRV
215 | MALLOC_LARGE 000000011b748000-000000011b74c000 [ 16K] rw-/rwx SM=PRV
216 |
217 | Termination Signal: Bus error: 10
218 | Termination Reason: Namespace SIGNAL, Code 0xa
219 | Terminating Process: exc handler [4801]
220 | Triggered by Thread: 38
221 |
222 | Thread 0 name:
223 | Thread 0:
224 | 0 libsystem_kernel.dylib 0x00000001b92c8c04 mach_msg_trap + 8
225 | 1 libsystem_kernel.dylib 0x00000001b92c8020 mach_msg + 76 (mach_msg.c:103)
226 | 2 CoreFoundation 0x00000001b947b964 __CFRunLoopServiceMachPort + 220 (CFRunLoop.c:2575)
227 | 3 CoreFoundation 0x00000001b94767fc __CFRunLoopRun + 1428 (CFRunLoop.c:2931)
228 | 4 CoreFoundation 0x00000001b9475f40 CFRunLoopRunSpecific + 480 (CFRunLoop.c:3192)
229 | 5 GraphicsServices 0x00000001c36f3534 GSEventRunModal + 108 (GSEvent.c:2246)
230 | 6 UIKitCore 0x00000001bd5eea60 UIApplicationMain + 1940 (UIApplication.m:4773)
231 | 7 UnityFramework 0x000000010281a330 -[UnityFramework runUIApplicationMainWithArgc:argv:] + 108 (main.mm:96)
232 | 8 demo 0x0000000100eb7e1c main + 68 (main.mm:26)
233 | 9 libdyld.dylib 0x00000001b92f4e18 start + 4
234 |
235 | Thread 1:
236 | 0 libsystem_pthread.dylib 0x00000001b92119e0 start_wqthread + 0
237 |
238 | Thread 2:
239 | 0 libsystem_pthread.dylib 0x00000001b92119e0 start_wqthread + 0
240 |
241 | ····
242 | Thread 38 Crashed:
243 | 0 libsystem_platform.dylib 0x00000001b9206688 _platform_memmove + 408
244 | 1 UnityFramework 0x000000010334f840 il2cpp::icalls::mscorlib::System::Buffer::BlockCopyInternal(Il2CppArray*, int, Il2CppArray*, int, int) + 176
245 | 2 UnityFramework 0x0000000103aaee4c StreamWriter_Write_m8056BDE8A4AD4816F9D7DBDBCB80D03BE8F3ED14 + 192 (mscorlib9.cpp:21738)
246 | 3 UnityFramework 0x0000000103ab22f4 UnexceptionalStreamWriter_Write_mAC310C8D24F673608DC7F130666E572ADD388E07 + 92 (mscorlib9.cpp:24996)
247 | 4 UnityFramework 0x0000000103729968 Logger_Log_mC3BD9B8C40067382718BD9A54688A92599404E6B + 240 (SmartFox2X2.cpp:32959)
248 | 5 UnityFramework 0x00000001036f8548 UDPManager_OnUDPData_mCD9304C4A4BD03D6F85EFAD9512893D4B2D037DC + 396 (SmartFox2X1.cpp:19687)
249 | 6 UnityFramework 0x0000000103712ec0 OnDataDelegate_Invoke_mF1F76E2DCD9F8028B8956AE76035840B21615920 + 488 (SmartFox2X1.cpp:0)
250 | 7 UnityFramework 0x00000001037175fc ThreadManager_InThread_m70E3D0A1EBE6F3AF67DFE1E8D50F54463DA49D10 + 180 (SmartFox2X2.cpp:18993)
251 | 8 UnityFramework 0x00000001039fcb98 ThreadStart_Invoke_m11B6A66E82F02C74399A7314C14C7F52393CC4B4 + 284 (ClassInlines.h:0)
252 | 9 UnityFramework 0x00000001039d0940 ContextCallback_Invoke_m76E65E7A67AA99858554F451E0F78E22B6478998 + 440 (ClassInlines.h:0)
253 | 10 UnityFramework 0x00000001039d1bf8 ExecutionContext_RunInternal_m5BF955CE8B04D9A7C959B9011579CC0FABD5FC56 + 316 (mscorlib19.cpp:30738)
254 | 11 UnityFramework 0x00000001039fcb98 ThreadStart_Invoke_m11B6A66E82F02C74399A7314C14C7F52393CC4B4 + 284 (ClassInlines.h:0)
255 | 12 UnityFramework 0x000000010286b620 RuntimeInvoker_TrueVoid_t22962CB4C05B1D89B55A6E1139F0E87A90987017(void (*)(), MethodInfo const*, void*, void**) + 20 (Il2CppInvokerTable.cpp:18279)
256 | 13 UnityFramework 0x00000001033925c8 il2cpp::vm::Runtime::Invoke(MethodInfo const*, void*, void**, Il2CppException**) + 116
257 | 14 UnityFramework 0x000000010335c678 il2cpp::icalls::mscorlib::System::Threading::ThreadStart(void*) + 108
258 | 15 UnityFramework 0x0000000103365a7c il2cpp::os::Thread::RunWrapper(void*) + 88
259 | 16 UnityFramework 0x0000000103367d14 il2cpp::os::ThreadImpl::ThreadStartWrapper(void*) + 40
260 | 17 libsystem_pthread.dylib 0x00000001b9209840 _pthread_start + 168 (pthread.c:896)
261 | 18 libsystem_pthread.dylib 0x00000001b92119f4 thread_start + 8
262 |
263 | ····
264 |
265 | Binary Images:
266 | 0x100eb0000 - 0x100eb7fff demo arm64 <14cde28b305d3bffb8abdfab5a30df25> /var/containers/Bundle/Application/03D4FE4F-26BC-47E5-A1D9-B8FF1D379B4A/demo.app/demo
267 | 0x100f80000 - 0x100f8bfff libobjc-trampolines.dylib arm64e <028df612175838e498958fd8c6a9a03c> /usr/lib/libobjc-trampolines.dylib
268 |
269 | ```
270 | 上面省略了其他线程的信息
271 |
272 | - Incident Identifier : Crash唯一ID
273 | - Hardware Model: 设备型号
274 | - Process: 可执行文件名
275 | - Identifier :Bundle ID
276 | - Code Type : CPU架构
277 | - OS Version: 系统版本号号
278 | - Report Version :崩溃日志版本号
279 | - Exception Type - Termination Reason: 是异常的类型和原因
280 | - Triggered by Thread: 38 : 引发的线程
281 | - Thread 0: 主线程
282 | - 崩溃发生的堆栈 (位置, 地址, 函数符号+ 偏移量):
283 | > 0 libsystem_kernel.dylib 0x00000001b92c8c04 mach_msg_trap + 8
284 |
285 | - Binary Images: 二进制文件内存信息 ( 地址起始-地址结束 名称 CPU架构 **UIID** 路径)
286 | > 0x100eb0000 - 0x100eb7fff demo arm64 <14cde28b305d3bffb8abdfab5a30df25> /var/containers/Bundle/Application/03D4FE4F-26BC-47E5-A1D9-B8FF1D379B4A/demo.app/demo
287 |
288 | 更多详情:
289 |
290 | - [iOS Crash日志结构介绍](https://jianli2017.top/wiki/IOS/crash/1_system_Crash_Type/)
291 | - [了解和分析iOS Crash Report](https://juejin.im/post/6844903774780145678)
292 |
293 |
294 |
295 | 对于Debug环境中我们拿到的Crash日志通常都是带有符号的,也就是我们可以追踪到具体的crash方法和行号。如果Crash堆栈没有解析符号表,这种情况就需要我们手动解析符号表了,当然很多Crash平台都集成了解析符号表的功能,我们只需要自己上传即可。下面我们就介绍下符号表相关的知识。
296 |
297 | ### 解析符号表
298 | 为了能快速并准确地定位用户APP发生Crash的代码位置,我们使用符号表对APP发生Crash的程序堆栈进行解析和还原, 比如一个iOS的crash堆栈:
299 | 
300 | ##### 符号表
301 | 简单来说,符号表是内存地址与函数名,文件名,行号的映射表
302 | > <起始地址> <结束地址> <函数> [<文件名:行号>]
303 |
304 |
305 | - 对于Android工程,一般情况下我们采用 il2cpp 方式构建,unity-2018.4会把符号表自动打包成一个压缩包文件。后缀 `.symbols.zip`, 存放位置和 `.apk` 文件在同一目录。
306 | 
307 |
308 | - iOS系统的下的符号表就是dSYM文件,文件名通常为:xxx.app.dSYM。我们使用xcode编译完项目后,会生产dSYM文件,和app文件在同一个目录下
309 |
310 | 符号表相关的知识参考:
311 | Bugly官方手册 - [Android符号表](https://bugly.qq.com/docs/user-guide/symbol-configuration-android/?v=20200622202242),[iOS符号表](https://bugly.qq.com/docs/user-guide/symbol-configuration-ios/?v=20200622202242)
312 |
313 | ##### 解析符号表
314 | 拿到符号表,并且确认**UIID一致**的情况下,我们就可以进行工具进行堆栈符号解析了。
315 |
316 | - Android平台下可以使用NDK库中 `add2line ` `ndk-stack` 工具,`ndk-stack`可以对Android的Crash-Log进行提取和解析,非常方便在拿到Crashlog的情况下分析。
317 | 我们通过以下命名可以获取实时的Crash信息
318 | > adb shell logcat | ndk-stack -sym $PROJECT_PATH/obj/local/armeabi
319 | 更多内容请翻阅:
320 | [Android Native crash日志分析](https://www.cnblogs.com/willhua/p/6718379.html)
321 |
322 | - iOS平台下两种工具`symbolicatecrash ` 和`atos ` ,`symbolicatecrash`其实就是对`atos`的封装
323 | 更多内容请翻阅:
324 | [OS Crash分析必备:符号化系统库方法](https://zuikyo.github.io/2016/12/18/iOS%20Crash%E6%97%A5%E5%BF%97%E5%88%86%E6%9E%90%E5%BF%85%E5%A4%87%EF%BC%9A%E7%AC%A6%E5%8F%B7%E5%8C%96%E7%B3%BB%E7%BB%9F%E5%BA%93%E6%96%B9%E6%B3%95/)
325 |
326 |
327 |
328 | ## Unity上报平台的选择
329 |
330 | ### 主流上报平台的对比分析
331 | Unity异常上报通常有四家较为主流的平台,
332 |
333 | - [Bugly](https://bugly.qq.com/docs/user-guide/instruction-manual-plugin-unity/?v=20200312155538)
334 | - [Appcenter](https://docs.microsoft.com/en-gb/appcenter/sdk/crashes/unity)
335 | - [Unity Cloud Diagnostics](https://unitytech.github.io/clouddiagnostics/)
336 | - [Firebase](https://firebase.google.com/docs/crashlytics/get-started?authuser=0&platform=unity)
337 |
338 | 这里对各个平台的特点梳理了一个表格,集成后可以参阅(标绿的部分是**明显优势项**,表格中仅拿Android项目进行了测试统计):
339 | 
340 |
341 | 根据咱们公司风控要求,**Bugly仅限于只在国内发行的App使用**,**Firebase当前阶段禁止使用**,因此产品的集成平台是建议选用以下组合:
342 | > 国内产品推荐:Bugly, Unity
343 | >
344 | > 海外产品推荐:Appcenter, Unity
345 |
346 |
347 | ### 搭建自己的上报平台
348 | 前面介绍了第三方Crash或者Unity平台,假如我们需要上报到自己的平台,需要分几个方面进行处理,这些可以单独进行集成:
349 | 1. Exception 捕获
350 | 2. Native Crash, 包括Android和iOS平台
351 |
352 | #### 全局异常处理
353 | 前面已经讲过 `Application.logMessageReceivedThreaded`注册接口可以获取全局的Exception,所以Unity托段代码的异常获得还是非常简单的,实现对应的接口方法即可监听。
354 |
355 | #### iOS 异常
356 | iOS平台提供了捕获异常的接口:
357 |
358 | ```
359 | /* Set the uncaught exception handler */
360 | NSSetUncaughtExceptionHandler(&uncaught_exception_handler);
361 | ```
362 | 如果同时使用了注册了第三方的Crash平台, 需要先保存第三方的 ExceptionHandler,然后在设置自己处理 exception 的 ExceptionHandler,在自己的 ExceptionHandler 处理完异常之后,再将异常交给第三方处理。
363 |
364 | 如果需要集成服务端解析符号表,请参考:
365 | [有赞 crash 平台符号化实践](https://www.infoq.cn/article/JLtuf3PGfJV6ovjzu1sq?utm_source=related_read_bottom&utm_medium=article)
366 |
367 | #### Android Java 异常
368 | Android平台也提供了处理Java异常的全局接口 `Thread.UncaughtExceptionHandler`, 我们实现此接口通过
369 | `Thread.setDefaultUncaughtExceptionHandler` 注册给系统即可。同样iOS平台类似,应为此接口值接受一个Handler,需要先通过 `Thread.getDefaultUncaughtExceptionHandler` 获取之前的Handler,再根据需要将捕获的Exception进行转交。
370 |
371 | #### Android Native 异常
372 | Android 平台可以使用 [google breakpad](https://github.com/google/breakpad) 捕获native crash, Appcenter平台就是采用的该方案。
373 |
374 |
375 | ## 异常处理和上报
376 |
377 | 当我们了解了所有的异常类型,也选择了合适的上报平台,最后就可以进行异常上报了。通常第三方都会主动收集异常信息,`AppCenter`这样的平台甚至会主动上报Error的log, `Bugly`的Android平台还是收集 logcat日志信息。 除了平台做的这些事情我们还需要注意两件非常重要的事情,来快速定位Crash:
378 |
379 | 1. 恰当的处理异常:异常的信息能够发现具体的**异常原因** 和 **异常发生位置**
380 | 2. 上报异常之外的其他信息,进行辅助判断,因为很多的native异常位置指向性并不能定位原因。
381 |
382 | #### 恰当的处理异常
383 |
384 | ##### 正确使用try-catch-finially语句处理异常。
385 |
386 | - `try`的作用域尽量单一,方便错误定位。
387 | - `catch`中做恢复资源,如果不能正确处理请上报事件或debug下输出错误,最后或者是重新throw。
388 | - `finally`中清理资源,可以不`catch` Exception,仅使用try-finally做资源闭环处理。
389 |
390 |
391 | 另外更多使用规则请参阅
392 | [Best practices for exceptions](https://docs.microsoft.com/en-us/dotnet/standard/exceptions/best-practices-for-exceptions)
393 |
394 | ##### throw 关键字使用
395 |
396 |
397 | * `throw [e];` 抛出一个异常,调用栈是当前调用方法。
398 |
399 | 注意: 当try-catch中 抛出一个新的异常,捕获的异常的调用栈并不是真正错误来源。如果需要保留catch的Exception,需要使用下面的构建方法:
400 | > `public Exception(string message, Exception innerException);`
401 |
402 | 一个常见的用法:
403 |
404 | ```c#
405 | public class ProcessFile
406 | {
407 | public static void Main()
408 | {
409 | FileStream fs;
410 | try
411 | {
412 | fs = new FileStream("data.txt", FileMode.Open);
413 | var sr = new StreamReader(fs);
414 | // ....
415 | }
416 | catch(FileNotFoundException e)
417 | {
418 | Console.WriteLine($"[Data File Missing] {e}");
419 | throw new FileNotFoundException(@"[data.txt not in c:\temp directory]", e);
420 | }
421 | finally
422 | {
423 | if (fs != null)
424 | fs.Close();
425 | }
426 | }
427 | }
428 | ```
429 |
430 | * `throw;` 重新抛出try-catch的异常 ,通常可作为代码异常的监听,但不做拦截
431 |
432 | ##### 方便确定异常发生的位置
433 | il2cpp目前并不记录异常代码的行号,这对定位产生了一定的困难。以下几个点可以让我们更加方便确定异常的位置:
434 |
435 | - 编写职责单一的方法,方法行数不易过多。
436 | - 异常捕获时提供额外信息。
437 | - 准确记录堆栈路径,通常是自己构建Exception时注意保留Catch的Exception堆栈。
438 |
439 |
440 | #### 上报关键信息
441 |
442 | 我们可以通过反馈入口或者异常捕获拿拿到线上用户的现场信息,为了方便定位Crash,应当尽量提供一下现场信息
443 |
444 | - 设备信息: 包括设备基本类型,系统版本,系统语言,定义的UIID, 内存情况,存储情况(需要单独实现Native接口)等
445 |
446 | ```
447 | SystemInfo.deviceModel,
448 | SystemInfo.deviceName,
449 | SystemInfo.deviceType,
450 | SystemInfo.deviceUniqueIdentifier,
451 | SystemInfo.operatingSystem,
452 | Localization.language,
453 |
454 | SystemInfo.systemMemorySize,
455 | SystemInfo.processorCount,
456 | SystemInfo.processorType,
457 | Screen.currentResolution.width,
458 | Screen.currentResolution.height,
459 | Screen.dpi,
460 | Screen.fullScreen,
461 |
462 | SystemInfo.graphicsDeviceName,
463 | SystemInfo.graphicsDeviceVendor,
464 | SystemInfo.graphicsMemorySize,
465 | SystemInfo.graphicsPixelFillrate,
466 | SystemInfo.maxTextureSize,
467 | ```
468 |
469 | - 应用信息:运行时间,异常时所在界面,用户操作的动作链等
470 | - Trace事件:为了方便业务分析,我们在开发过程会上报事件用户埋点分析,除此之外我们可以提供Trace事件类型,主要来统计模块加载和错误输出,发生异常是可以根据场景附加上多个事件集合。
471 | - 日志文件:在可能的情况下,附加当前应用的日志文件,AppCenter提供了附加文件的接口,我们可以将日志文件一并上传。
472 | - 反馈接口:用户提供额外的文字描述和截屏。
473 |
474 | ## 测试题
475 | 1. 关于Unity C# Excepction,下列说法正确的是:
476 |
477 | A. Android程序发生了 C# Exception默认情况下会造成Crash闪退。
478 |
479 | B. `Fast but no Exceptions` 的意思是运行速度快并且不会产生Crash闪退。
480 |
481 | C. 使用 `public Exception(string message, Exception innerException); ` 构造方法可以保留innerException的调用堆栈。
482 |
483 | D. try-catch 不能捕获协程中的异常
484 |
485 | 答案: CD
486 |
487 | 2. Unity LogCallback接口就是日志的回调,可以获得日志,但不能拿到Exception的实例
488 |
489 | 答案: 对(只能拿到Exception的message和callstack属性)
490 |
491 | 3. Native Crash 下列说法错误的是:
492 | A. Java Excpeption会造成程序终止
493 | B. 在符号表的前提下, iOS Crash可以获得异常堆栈的行号
494 | C. 可以使用 `ndk-stack` 和 `atos` 解析 Android native crash
495 | D. dSYM 是iOS编译过程中生成的符号表文件
496 |
497 | 答案 : C
498 |
499 | 4. 虽然国内的Android产品不能使用 `Firebase` ,但海外的产品都可以使用
500 |
501 | 答案 错
502 |
503 | 5. 下列说法正确的是:
504 |
505 | A. throw 关键字可以单独使用,不用跟随Exception对象。
506 | B. 可以不适用catch语句,使用try-finally进行及时的资源释放。
507 | C. 日志文件可以让我们更快的定位Crash原因,各大平台中仅bugly提供Logcat日志获取。
508 | D. 为了方便定位错误发生位置,应当编写职责单一的方法,方法行数不易过多。
509 |
510 | 答案: ABCD
511 |
512 |
513 |
--------------------------------------------------------------------------------
/exception-common.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sundxing/Unity-Exception-Crash/c20f1cbbd9e37a4af6e1303b7ec5e4384d5094f3/exception-common.png
--------------------------------------------------------------------------------
/symbol-path.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sundxing/Unity-Exception-Crash/c20f1cbbd9e37a4af6e1303b7ec5e4384d5094f3/symbol-path.png
--------------------------------------------------------------------------------
/symbol-retrace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sundxing/Unity-Exception-Crash/c20f1cbbd9e37a4af6e1303b7ec5e4384d5094f3/symbol-retrace.png
--------------------------------------------------------------------------------