| <html devsite><head> |
| <title>诊断原生代码崩溃问题</title> |
| <meta name="project_path" value="/_project.yaml"/> |
| <meta name="book_path" value="/_book.yaml"/> |
| </head> |
| <body> |
| <!-- |
| Copyright 2017 The Android Open Source Project |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| --> |
| |
| <p> |
| 以下部分包括原生代码崩溃的常见类型、对示例崩溃转储的分析以及有关 tombstone 的讨论。每种崩溃类型都包括示例 <code>debuggerd</code> 输出,其中突出显示的关键证据可以帮助您区分特定类型的崩溃问题。 |
| </p> |
| |
| <aside class="tip"> |
| <strong>提示</strong>:如果您之前从未遇到过原生代码崩溃问题,请从<a href="/devices/tech/debug/index.html">调试原生 Android 平台代码</a>着手。 |
| </aside> |
| |
| <h2 id="abort">中止</h2> |
| |
| <p> |
| 中止操作很有趣,因为这是刻意而为。执行中止操作可通过多种不同的方法(包括调用 <code><a href="http://man7.org/linux/man-pages/man3/abort.3.html" class="external">abort(3)</a></code>、使 <code><a href="http://man7.org/linux/man-pages/man3/assert.3.html" class="external">assert(3)</a></code> 失败、使用 Android 特有的严重记录类型之一)来实现,但所有这些方法都涉及到调用 <code>abort</code>。<code>abort</code> 调用会向调用线程发出 SIGABRT 信号,因此为了识别这种情况,您需要在 <code>debuggerd</code> 输出中查找以下两项内容:<code>libc.so</code> 中显示“abort”的帧,以及 SIGABRT 信号。 |
| </p> |
| |
| <p> |
| 您可能会看到明确的“中止消息”行。不过,您还应该查看 <code>logcat</code> 输出,了解此线程在刻意终止自身之前所记录的内容,因为与 <code>assert(3)</code> 或高级别的严重记录设备不同的是,<code>abort(3)</code> 不接受任何消息。 |
| </p> |
| |
| <p> |
| 当前版本的 Android 内嵌了 <code><a href="http://man7.org/linux/man-pages/man2/tgkill.2.html" class="external">tgkill(2)</a></code> 系统调用,因此它们的堆栈是最容易读取的,因为对 abort(3) 进行的调用位于最顶端: |
| </p> |
| |
| <pre class="devsite-click-to-copy"> |
| pid: 4637, tid: 4637, name: crasher >>> crasher <<< |
| signal 6 (<em style="color:Orange">SIGABRT</em>), code -6 (SI_TKILL), fault addr -------- |
| <em style="color:Orange">Abort message</em>: 'some_file.c:123: some_function: assertion "false" failed' |
| r0 00000000 r1 0000121d r2 00000006 r3 00000008 |
| r4 0000121d r5 0000121d r6 ffb44a1c r7 0000010c |
| r8 00000000 r9 00000000 r10 00000000 r11 00000000 |
| ip ffb44c20 sp ffb44a08 lr eace2b0b pc eace2b16 |
| backtrace: |
| #00 pc 0001cb16 /system/lib/<em style="color:Orange">libc.so</em> (<em style="color:Orange">abort</em>+57) |
| #01 pc 0001cd8f /system/lib/libc.so (__assert2+22) |
| #02 pc 00001531 /system/bin/crasher (do_action+764) |
| #03 pc 00002301 /system/bin/crasher (main+68) |
| #04 pc 0008a809 /system/lib/libc.so (__libc_init+48) |
| #05 pc 00001097 /system/bin/crasher (_start_main+38) |
| </pre> |
| |
| <p> |
| 在原始中止调用(此处为帧 4)与实际发送信号(此处为帧 0)之间,较低版本的 Android 需要遵循复杂的路径。特别是在 32 位 ARM 上运行的 Android,它会将 <code>__libc_android_abort</code>(此处为帧 3)添加到其他平台的 <code>raise</code>/<code>pthread_kill</code>/<code>tgkill</code> 序列:</p> |
| |
| <pre class="devsite-click-to-copy"> |
| pid: 1656, tid: 1656, name: crasher >>> crasher <<< |
| signal 6 (<em style="color:Orange">SIGABRT</em>), code -6 (SI_TKILL), fault addr -------- |
| <em style="color:Orange">Abort message</em>: 'some_file.c:123: some_function: assertion "false" failed' |
| r0 00000000 r1 00000678 r2 00000006 r3 f70b6dc8 |
| r4 f70b6dd0 r5 f70b6d80 r6 00000002 r7 0000010c |
| r8 ffffffed r9 00000000 sl 00000000 fp ff96ae1c |
| ip 00000006 sp ff96ad18 lr f700ced5 pc f700dc98 cpsr 400b0010 |
| backtrace: |
| #00 pc 00042c98 /system/lib/libc.so (tgkill+12) |
| #01 pc 00041ed1 /system/lib/libc.so (pthread_kill+32) |
| #02 pc 0001bb87 /system/lib/libc.so (raise+10) |
| #03 pc 00018cad /system/lib/libc.so (__libc_android_abort+34) |
| #04 pc 000168e8 /system/lib/<em style="color:Orange">libc.so</em> (<em style="color:Orange">abort</em>+4) |
| #05 pc 0001a78f /system/lib/libc.so (__libc_fatal+16) |
| #06 pc 00018d35 /system/lib/libc.so (__assert2+20) |
| #07 pc 00000f21 /system/xbin/crasher |
| #08 pc 00016795 /system/lib/libc.so (__libc_init+44) |
| #09 pc 00000abc /system/xbin/crasher |
| </pre> |
| |
| <p> |
| 您可以使用 <code>crasher |
| abort</code> 来重现这类崩溃问题的实例。 |
| </p> |
| |
| <h2 id="nullpointer">纯 Null 指针解引用</h2> |
| |
| <p> |
| 这是典型的原生代码崩溃问题,虽然它只是下一类崩溃问题的特殊情况,但值得单独说明,因为这类崩溃问题通常无需细细思量。 |
| </p> |
| |
| <p> |
| 在以下示例中,尽管崩溃函数在 <code>libc.so</code> 中,但由于字符串函数仅在指定给它们的指针处进行操作,因此您可以推断出在调用 <code><a href="http://man7.org/linux/man-pages/man3/strlen.3.html" class="external">strlen(3)</a></code> 时指定的是 Null 指针;对于这类崩溃问题,应直接找调用代码的作者加以解决。在这种情况下,帧 #01 是不良调用程序。 |
| </p> |
| |
| <pre class="devsite-click-to-copy"> |
| pid: 25326, tid: 25326, name: crasher >>> crasher <<< |
| signal 11 (<em style="color:Orange">SIGSEGV</em>), code 1 (SEGV_MAPERR), <em style="color:Orange">fault addr 0x0</em> |
| r0 00000000 r1 00000000 r2 00004c00 r3 00000000 |
| r4 ab088071 r5 fff92b34 r6 00000002 r7 fff92b40 |
| r8 00000000 r9 00000000 sl 00000000 fp fff92b2c |
| ip ab08cfc4 sp fff92a08 lr ab087a93 pc efb78988 cpsr 600d0030 |
| |
| backtrace: |
| #00 pc 00019988 /system/lib/libc.so (strlen+71) |
| #01 pc 00001a8f /system/xbin/crasher (strlen_null+22) |
| #02 pc 000017cd /system/xbin/crasher (do_action+948) |
| #03 pc 000020d5 /system/xbin/crasher (main+100) |
| #04 pc 000177a1 /system/lib/libc.so (__libc_init+48) |
| #05 pc 000010e4 /system/xbin/crasher (_start+96) |
| </pre> |
| |
| <p> |
| 您可以使用 <code>crasher |
| strlen-NULL</code> 重现这类崩溃问题的实例。 |
| </p> |
| |
| <h2 id="lowaddress">低地址 Null 指针解引用</h2> |
| |
| <p> |
| 在许多情况下,故障地址将不会是 0,而是其他一些小数字。两位或三位地址尤其常见,而六位地址几乎肯定不是 Null 指针解引用(它需要 1 MiB 的偏移量)。通常,当您的代码将 Null 指针解引用为看似有效的结构时,就会出现这种情况。常见的函数是 <code><a href="http://man7.org/linux/man-pages/man3/fprintf.3.html" class="external">fprintf(3)</a></code>(或任何使用 FILE* 的其他函数)和 <code><a href="http://man7.org/linux/man-pages/man3/readdir.3.html" class="external">readdir(3)</a></code>,因为代码通常无法检查到底是 <code><a href="http://man7.org/linux/man-pages/man3/fopen.3.html" class="external">fopen(3)</a></code> 调用先成功还是 <code><a href="http://man7.org/linux/man-pages/man3/opendir.3.html" class="external">opendir(3)</a></code> 调用先成功。 |
| </p> |
| |
| <p> |
| 以下是 <code>readdir</code> 的示例: |
| </p> |
| |
| <pre class="devsite-click-to-copy"> |
| pid: 25405, tid: 25405, name: crasher >>> crasher <<< |
| signal 11 (<em style="color:Orange">SIGSEGV</em>), code 1 (SEGV_MAPERR), <em style="color:Orange">fault addr 0xc</em> |
| r0 0000000c r1 00000000 r2 00000000 r3 3d5f0000 |
| r4 00000000 r5 0000000c r6 00000002 r7 ff8618f0 |
| r8 00000000 r9 00000000 sl 00000000 fp ff8618dc |
| ip edaa6834 sp ff8617a8 lr eda34a1f pc eda618f6 cpsr 600d0030 |
| |
| backtrace: |
| #00 pc 000478f6 /system/lib/libc.so (pthread_mutex_lock+1) |
| #01 pc 0001aa1b /system/lib/libc.so (readdir+10) |
| #02 pc 00001b35 /system/xbin/crasher (readdir_null+20) |
| #03 pc 00001815 /system/xbin/crasher (do_action+976) |
| #04 pc 000021e5 /system/xbin/crasher (main+100) |
| #05 pc 000177a1 /system/lib/libc.so (__libc_init+48) |
| #06 pc 00001110 /system/xbin/crasher (_start+96) |
| </pre> |
| |
| <p> |
| 在此示例中,崩溃问题的直接原因是 <code><a href="http://man7.org/linux/man-pages/man3/pthread_mutex_lock.3p.html" class="external">pthread_mutex_lock(3)</a></code> 曾尝试访问地址 0xc(帧 0)。但是 <code>pthread_mutex_lock</code> 执行的第一项操作是解引用指定给它的 <code>pthread_mutex_t*</code> 的 <code>state</code> 元素。如果您查看源代码,则会发现该元素在结构中的偏移量为零,这表示指定给 <code>pthread_mutex_lock</code> 的指针 0xc 无效。从帧 1 可以看出,<code>readdir</code> 会将该指针指定给它,这会从指定的 <code>DIR*</code> 中提取 <code>mutex_</code> 字段。通过查看该结构,您会发现 <code>struct DIR</code> 中 <code>mutex_</code> 的偏移量为 <code>sizeof(int) + sizeof(size_t) + sizeof(dirent*)</code>,在 32 位设备上表示为 4 + 4 + 4 = 12 = 0xc,这样便可以找到错误所在:调用程序向 <code>readdir</code> 传递了一个 Null 指针。此时,您可以将该堆栈粘贴到堆栈工具中,以找出这个问题在 logcat 中的发生位置。<em></em> |
| </p> |
| |
| <pre class="prettyprint"> |
| struct DIR { |
| int fd_; |
| size_t available_bytes_; |
| dirent* next_; |
| pthread_mutex_t mutex_; |
| dirent buff_[15]; |
| long current_pos_; |
| }; |
| </pre> |
| |
| <p> |
| 其实在大多数情况下,您可以跳过此分析。一个充分的低位故障地址通常意味着您可以跳过堆栈中的任意 <code>libc.so</code> 帧,并直接归咎于调用的代码。不过,情况并非总是如此,这些例外将是您用作展示的绝佳机会。 |
| </p> |
| |
| <p> |
| 您可以使用 <code>crasher |
| fprintf-NULL</code> 或 <code>crasher readdir-NULL</code> 重现此类崩溃问题的实例。 |
| </p> |
| |
| <h2 id="fortify">FORTIFY 失败</h2> |
| |
| <p> |
| FORTIFY 失败是中止的一种特殊情况,当 C 库检测到可能导致安全漏洞的问题时,就会发生 FORTIFY 失败。很多 C 库函数已得到加强;它们需要一个额外的参数来确定缓冲区的实际大小,并在运行时检查您尝试执行的操作是否真的合理。<em></em>以下示例显示了代码尝试使用 <code>read(fd, buf, 32)</code> 读入实际上只有 10 字节长的缓冲区… |
| </p> |
| |
| <pre class="devsite-click-to-copy"> |
| pid: 25579, tid: 25579, name: crasher >>> crasher <<< |
| signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr -------- |
| Abort message: '<em style="color:Orange">FORTIFY: read: prevented 32-byte write into 10-byte buffer</em>' |
| r0 00000000 r1 000063eb r2 00000006 r3 00000008 |
| r4 ff96f350 r5 000063eb r6 000063eb r7 0000010c |
| r8 00000000 r9 00000000 sl 00000000 fp ff96f49c |
| ip 00000000 sp ff96f340 lr ee83ece3 pc ee86ef0c cpsr 000d0010 |
| |
| backtrace: |
| #00 pc 00049f0c /system/lib/libc.so (tgkill+12) |
| #01 pc 00019cdf /system/lib/libc.so (abort+50) |
| #02 pc 0001e197 /system/lib/libc.so (<em style="color:Orange">__fortify_fatal</em>+30) |
| #03 pc 0001baf9 /system/lib/libc.so (__read_chk+48) |
| #04 pc 0000165b /system/xbin/crasher (do_action+534) |
| #05 pc 000021e5 /system/xbin/crasher (main+100) |
| #06 pc 000177a1 /system/lib/libc.so (__libc_init+48) |
| #07 pc 00001110 /system/xbin/crasher (_start+96) |
| </pre> |
| |
| <p> |
| 您可以使用 <code>crasher |
| fortify</code> 来重现这类崩溃问题的实例。 |
| </p> |
| |
| <h2 id="stackcorruption">-fstack-protector 检测到的堆栈损坏</h2> |
| |
| <p> |
| 编译器的 <code>-fstack-protector</code> 选项会在具有栈上缓冲区的函数中插入检查机制,以防止缓冲区溢出。默认情况下,系统会为平台代码(而非应用)启用此选项。启用此选项后,编译器会向<a href="https://en.wikipedia.org/wiki/Function_prologue" class="external">函数序言</a>添加指令,以在堆栈上写入刚刚超过上一局部值的随机值,并向函数结尾添加指令以进行回读并确认是否发生更改。如果该值已更改,则表示该值已被缓冲区溢出覆盖,因此该结尾会调用 <code>__stack_chk_fail</code> 来记录消息和中止。 |
| </p> |
| |
| <pre class="devsite-click-to-copy"> |
| pid: 26717, tid: 26717, name: crasher >>> crasher <<< |
| signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr -------- |
| <em style="color:Orange">Abort message: 'stack corruption detected'</em> |
| r0 00000000 r1 0000685d r2 00000006 r3 00000008 |
| r4 ffd516d8 r5 0000685d r6 0000685d r7 0000010c |
| r8 00000000 r9 00000000 sl 00000000 fp ffd518bc |
| ip 00000000 sp ffd516c8 lr ee63ece3 pc ee66ef0c cpsr 000e0010 |
| |
| backtrace: |
| #00 pc 00049f0c /system/lib/libc.so (tgkill+12) |
| #01 pc 00019cdf /system/lib/libc.so (abort+50) |
| #02 pc 0001e07d /system/lib/libc.so (__libc_fatal+24) |
| #03 pc 0004863f /system/lib/libc.so (<em style="color:Orange">__stack_chk_fail</em>+6) |
| #04 pc 000013ed /system/xbin/crasher (smash_stack+76) |
| #05 pc 00001591 /system/xbin/crasher (do_action+280) |
| #06 pc 00002219 /system/xbin/crasher (main+100) |
| #07 pc 000177a1 /system/lib/libc.so (__libc_init+48) |
| #08 pc 00001144 /system/xbin/crasher (_start+96) |
| </pre> |
| |
| <p> |
| 您可以通过回溯中是否出现 <code>__stack_chk_fail</code> 以及特定的中止消息,将此中止与其他类型的中止区分开来。 |
| </p> |
| |
| <p> |
| 您可以使用 <code>crasher |
| smash-stack</code> 来重现这类崩溃问题的实例。 |
| </p> |
| |
| <h2 id="seccomp">来自不允许的系统调用的 Seccomp SIGSYS</h2> |
| |
| <p> |
| <a href="https://en.wikipedia.org/wiki/Seccomp" class="external">seccomp</a> 系统(具体是指 seccomp-bpf)会限制对系统调用的访问权限。要查看更多针对平台开发者的 seccomp 相关信息,请参阅博文 <a href="https://android-developers.googleblog.com/2017/07/seccomp-filter-in-android-o.html" class="external">Android O 中的 Seccomp 过滤器</a>。调用受限系统调用的线程将收到信号 SIGSYS 及代码 SYS_SECCOMP。系统调用编号将与架构一起显示在原因行中。需要注意的是,系统调用编号会因架构而异。例如,<code>readlinkat(2)</code> 系统调用在 x86 系统上的编号为 305,而在 x86-64 系统上的编号则为 267。在 arm 和 arm64 平台上,调用编号也不一样。因为系统调用编号因架构而异,所以在通常情况下,使用堆栈轨迹来找出不允许的系统调用比在标头中寻找系统调用编号更容易。 |
| </p> |
| |
| <pre class="devsite-click-to-copy"> |
| pid: 11046, tid: 11046, name: crasher >>> crasher <<< |
| signal 31 (SIGSYS), code 1 (<em style="color:Orange">SYS_SECCOMP</em>), fault addr -------- |
| <em style="color:Orange">Cause: seccomp prevented call to disallowed arm system call 99999</em> |
| r0 cfda0444 r1 00000014 r2 40000000 r3 00000000 |
| r4 00000000 r5 00000000 r6 00000000 r7 0001869f |
| r8 00000000 r9 00000000 sl 00000000 fp fffefa58 |
| ip fffef898 sp fffef888 lr 00401997 pc f74f3658 cpsr 600f0010 |
| |
| backtrace: |
| #00 pc 00019658 /system/lib/libc.so (syscall+32) |
| #01 pc 00001993 /system/bin/crasher (do_action+1474) |
| #02 pc 00002699 /system/bin/crasher (main+68) |
| #03 pc 0007c60d /system/lib/libc.so (__libc_init+48) |
| #04 pc 000011b0 /system/bin/crasher (_start_main+72) |
| </pre> |
| |
| <p> |
| 您可以根据信号行中是否出现 <code>SYS_SECCOMP</code> 以及原因行中的说明,将不允许的系统调用与其他崩溃问题区分开来。 |
| </p> |
| <p> |
| 您可以使用 <code>crasher |
| seccomp</code> 重现此类崩溃问题的实例。 |
| </p> |
| |
| <h2 id="crashdump">调查崩溃转储</h2> |
| |
| <p> |
| 如果您现在没有正在调查的特定崩溃问题,则平台来源包括用于测试 <code>debuggerd</code> 的工具,名为 crasher。如果您在 <code>system/core/debuggerd/</code> 中 <code>mm</code>,则您的路径中会出现 <code>crasher</code> 和 <code>crasher64</code>(您可以借助后者测试 64 位崩溃问题)。根据您提供的命令行参数,crasher 崩溃的方式多种多样。使用 <code>crasher --help</code> 可查看当前支持的选择。 |
| </p> |
| |
| <p> |
| 为了介绍崩溃转储中的各个方面,我们来看看以下崩溃转储示例: |
| </p> |
| |
| <pre class="devsite-click-to-copy"> |
| *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** |
| Build fingerprint: 'Android/aosp_flounder/flounder:5.1.51/AOSP/enh08201009:eng/test-keys' |
| Revision: '0' |
| ABI: 'arm' |
| pid: 1656, tid: 1656, name: crasher >>> crasher <<< |
| signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr -------- |
| Abort message: 'some_file.c:123: some_function: assertion "false" failed' |
| r0 00000000 r1 00000678 r2 00000006 r3 f70b6dc8 |
| r4 f70b6dd0 r5 f70b6d80 r6 00000002 r7 0000010c |
| r8 ffffffed r9 00000000 sl 00000000 fp ff96ae1c |
| ip 00000006 sp ff96ad18 lr f700ced5 pc f700dc98 cpsr 400b0010 |
| backtrace: |
| #00 pc 00042c98 /system/lib/libc.so (tgkill+12) |
| #01 pc 00041ed1 /system/lib/libc.so (pthread_kill+32) |
| #02 pc 0001bb87 /system/lib/libc.so (raise+10) |
| #03 pc 00018cad /system/lib/libc.so (__libc_android_abort+34) |
| #04 pc 000168e8 /system/lib/libc.so (abort+4) |
| #05 pc 0001a78f /system/lib/libc.so (__libc_fatal+16) |
| #06 pc 00018d35 /system/lib/libc.so (__assert2+20) |
| #07 pc 00000f21 /system/xbin/crasher |
| #08 pc 00016795 /system/lib/libc.so (__libc_init+44) |
| #09 pc 00000abc /system/xbin/crasher |
| Tombstone written to: /data/tombstones/tombstone_06 |
| *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** |
| </pre> |
| |
| <p> |
| 如果您要搜索原生代码崩溃问题的日志,则带有空格的星号行很有帮助。字符串“*** ***”很少出现在日志中,除了在原生代码崩溃问题开始的时候。 |
| </p> |
| |
| <pre class="devsite-click-to-copy"> |
| Build fingerprint: |
| 'Android/aosp_flounder/flounder:5.1.51/AOSP/enh08201009:eng/test-keys' |
| </pre> |
| |
| <p> |
| 您可以通过指纹准确识别崩溃问题发生的版本号。这与 <code>ro.build.fingerprint</code> 系统属性完全相同。 |
| </p> |
| |
| <pre class="devsite-click-to-copy"> |
| Revision: '0' |
| </pre> |
| |
| <p> |
| Revision 指的是硬件,而不是软件。通常情况下不使用 revision,但使用 revision 有助于您自动忽略由不良硬件导致的已知错误。这与 <code>ro.revision</code> 系统属性完全相同。 |
| </p> |
| |
| <pre class="devsite-click-to-copy"> |
| ABI: 'arm' |
| </pre> |
| |
| <p> |
| ABI 是 arm、arm64、mips、mips64、x86 或 x86-64 之一。这对上面提到的 <code>stack</code> 脚本最有用,这样它就知道要使用的工具链。 |
| </p> |
| |
| <pre class="devsite-click-to-copy"> |
| pid: 1656, tid: 1656, name: crasher >>> crasher <<< |
| </pre> |
| |
| <p> |
| 此行可标识崩溃进程中的特定线程。在这种情况下,它是进程的主线程,因此进程 ID 和线程 ID 一致。第一个名称是线程名称,在 >>> 和 <<< 中间的名称是进程名称。对于应用,进程名称通常是完全限定的文件包名称(如 com.facebook.katana),这在提交错误或尝试在 Google Play 中查找相应应用时很有用。在查找崩溃问题之前的相关日志行方面,pid 和 tid 也很有用。 |
| </p> |
| |
| <pre class="devsite-click-to-copy"> |
| signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr -------- |
| </pre> |
| |
| <p> |
| 您可从该行得知接收的信号 (SIGABRT) 以及有关如何接收该信号的更多信息 (SI_TKILL)。<code>debuggerd</code> 报告的信号是 SIGABRT、SIGBUS、SIGFPE、SIGILL、SIGSEGV 和 SIGTRAP。信号专用的代码因特定信号而异。 |
| </p> |
| |
| <pre class="devsite-click-to-copy"> |
| Abort message: 'some_file.c:123: some_function: assertion "false" failed' |
| </pre> |
| |
| <p>并非所有崩溃问题都会有中止消息行,但发生中止时,会出现该消息行。这是从此 pid/tid 的最后一行严重 logcat 输出中自动收集而来的,而在有意中止的情况下,这可以解释该程序自行终止的原因。 |
| </p> |
| |
| <pre class="devsite-click-to-copy"> |
| r0 00000000 r1 00000678 r2 00000006 r3 f70b6dc8 |
| r4 f70b6dd0 r5 f70b6d80 r6 00000002 r7 0000010c |
| r8 ffffffed r9 00000000 sl 00000000 fp ff96ae1c |
| ip 00000006 sp ff96ad18 lr f700ced5 pc f700dc98 cpsr 400b0010 |
| </pre> |
| |
| <p> |
| 寄存器转储显示收到信号时 CPU 寄存器的内容。(本区段在各 ABI 之间变化很大。)这些内容的有用程度取决于确切的崩溃问题。 |
| </p> |
| |
| <pre class="devsite-click-to-copy"> |
| backtrace: |
| #00 pc 00042c98 /system/lib/libc.so (tgkill+12) |
| #01 pc 00041ed1 /system/lib/libc.so (pthread_kill+32) |
| #02 pc 0001bb87 /system/lib/libc.so (raise+10) |
| #03 pc 00018cad /system/lib/libc.so (__libc_android_abort+34) |
| #04 pc 000168e8 /system/lib/libc.so (abort+4) |
| #05 pc 0001a78f /system/lib/libc.so (__libc_fatal+16) |
| #06 pc 00018d35 /system/lib/libc.so (__assert2+20) |
| #07 pc 00000f21 /system/xbin/crasher |
| #08 pc 00016795 /system/lib/libc.so (__libc_init+44) |
| #09 pc 00000abc /system/xbin/crasher |
| </pre> |
| |
| <p> |
| 您可以通过回溯得知崩溃问题发生时我们所处的代码位置。第一列是帧号(与 gdb 的样式一致,其中最底下的帧是 0)。PC 值与共享库的位置(而非绝对地址)相关。下一列是映射区域的名称(通常是共享库或可执行文件,但可能不适用于经过 JIT 编译的代码)。最后,如果有符号,则会显示与 PC 值对应的符号以及到该符号的偏移量(以字节为单位)。您可以结合使用此符号和 <code>objdump(1)</code> 来找到相应的编译器指令。 |
| </p> |
| |
| <h2 id="tombstones">读取 tombstone</h2> |
| |
| <pre class="devsite-click-to-copy"> |
| Tombstone written to: /data/tombstones/tombstone_06 |
| </pre> |
| |
| <p> |
| 您可由此得知 <code>debuggerd</code> 写入额外信息的位置。 |
| <code>debuggerd</code> 会保留最多 10 个 tombstone,从编号 00 至 09 循环并根据需要覆盖现有 tombstone。 |
| </p> |
| |
| <p> |
| Tombstone 包含与崩溃转储相同的信息,还包含一些其他信息。例如,它包含所有线程(不仅仅是崩溃线程)的回溯、浮点寄存器、原始堆栈转储,以及寄存器中地址附近的内存转储。<em></em>最有用的是,它还包含完整的内存映射(类似于 <code>/proc/<var>pid</var>/maps</code>)。以下是 32 位 ARM 进程崩溃的示例(带注释): |
| </p> |
| |
| <pre class="devsite-click-to-copy"> |
| memory map: (fault address prefixed with --->) |
| --->ab15f000-ab162fff r-x 0 4000 /system/xbin/crasher (BuildId: |
| b9527db01b5cf8f5402f899f64b9b121) |
| </pre> |
| |
| <p> |
| 这里需要注意两点。第一点是该行带有前缀“--->”。当您的崩溃问题不仅仅是 Null 指针解引用时,这些映射最有用。如果故障地址较小,则其很可能是 Null 指针解引用的某个变体。否则,通过查看故障地址周围的映射,您通常可以了解发生的问题。通过查看映射可识别的一些可能存在的问题包括: |
| </p> |
| |
| <ul> |
| <li>读/写延伸到内存块末尾之外。</li> |
| <li>在内存块开始之前读/写。</li> |
| <li>尝试执行非代码内容。</li> |
| <li>在堆栈末尾之外运行。</li> |
| <li>尝试写入代码(如上例所述)。</li> |
| </ul> |
| |
| <p> |
| 需要注意的第二点是,可执行文件和共享库文件将在 Android 6.0 和更高版本中显示 BuildId(如果有),因此您可以确切地看到崩溃代码的版本。从 Android 6.0 开始,平台二进制文件默认包含 BuildId。NDK r12 和更高版本还会自动将 <code>-Wl,--build-id</code> 传递到链接器。 |
| </p> |
| |
| <pre class="devsite-click-to-copy"> |
| ab163000-ab163fff r-- 3000 1000 /system/xbin/crasher |
| ab164000-ab164fff rw- 0 1000 |
| f6c80000-f6d7ffff rw- 0 100000 [anon:libc_malloc] |
| </pre> |
| |
| <p> |
| 在 Android 上,该堆不一定是单个区域。堆区域将被标记为 <code>[anon:libc_malloc]</code>。 |
| </p> |
| |
| <pre class="devsite-click-to-copy"> |
| f6d82000-f6da1fff r-- 0 20000 /dev/__properties__/u:object_r:logd_prop:s0 |
| f6da2000-f6dc1fff r-- 0 20000 /dev/__properties__/u:object_r:default_prop:s0 |
| f6dc2000-f6de1fff r-- 0 20000 /dev/__properties__/u:object_r:logd_prop:s0 |
| f6de2000-f6de5fff r-x 0 4000 /system/lib/libnetd_client.so (BuildId: 08020aa06ed48cf9f6971861abf06c9d) |
| f6de6000-f6de6fff r-- 3000 1000 /system/lib/libnetd_client.so |
| f6de7000-f6de7fff rw- 4000 1000 /system/lib/libnetd_client.so |
| f6dec000-f6e74fff r-x 0 89000 /system/lib/libc++.so (BuildId: 8f1f2be4b37d7067d366543fafececa2) (load base 0x2000) |
| f6e75000-f6e75fff --- 0 1000 |
| f6e76000-f6e79fff r-- 89000 4000 /system/lib/libc++.so |
| f6e7a000-f6e7afff rw- 8d000 1000 /system/lib/libc++.so |
| f6e7b000-f6e7bfff rw- 0 1000 [anon:.bss] |
| f6e7c000-f6efdfff r-x 0 82000 /system/lib/libc.so (BuildId: d189b369d1aafe11feb7014d411bb9c3) |
| f6efe000-f6f01fff r-- 81000 4000 /system/lib/libc.so |
| f6f02000-f6f03fff rw- 85000 2000 /system/lib/libc.so |
| f6f04000-f6f04fff rw- 0 1000 [anon:.bss] |
| f6f05000-f6f05fff r-- 0 1000 [anon:.bss] |
| f6f06000-f6f0bfff rw- 0 6000 [anon:.bss] |
| f6f0c000-f6f21fff r-x 0 16000 /system/lib/libcutils.so (BuildId: d6d68a419dadd645ca852cd339f89741) |
| f6f22000-f6f22fff r-- 15000 1000 /system/lib/libcutils.so |
| f6f23000-f6f23fff rw- 16000 1000 /system/lib/libcutils.so |
| f6f24000-f6f31fff r-x 0 e000 /system/lib/liblog.so (BuildId: e4d30918d1b1028a1ba23d2ab72536fc) |
| f6f32000-f6f32fff r-- d000 1000 /system/lib/liblog.so |
| f6f33000-f6f33fff rw- e000 1000 /system/lib/liblog.so |
| </pre> |
| |
| <p> |
| 通常,共享库会有 3 个相邻条目。一个是可读且可执行条目(代码),一个是只读条目(只读数据),还有一个是读写条目(可变数据)。第一列显示映射的地址范围,第二列显示权限(采用常规 Unix <code>ls(1)</code> 样式),第三列显示到文件的偏移量(十六进制),第四列显示区域大小(十六进制),第五列显示文件(或其他区域名称)。 |
| </p> |
| |
| <pre class="devsite-click-to-copy"> |
| f6f34000-f6f53fff r-x 0 20000 /system/lib/libm.so (BuildId: 76ba45dcd9247e60227200976a02c69b) |
| f6f54000-f6f54fff --- 0 1000 |
| f6f55000-f6f55fff r-- 20000 1000 /system/lib/libm.so |
| f6f56000-f6f56fff rw- 21000 1000 /system/lib/libm.so |
| f6f58000-f6f58fff rw- 0 1000 |
| f6f59000-f6f78fff r-- 0 20000 /dev/__properties__/u:object_r:default_prop:s0 |
| f6f79000-f6f98fff r-- 0 20000 /dev/__properties__/properties_serial |
| f6f99000-f6f99fff rw- 0 1000 [anon:linker_alloc_vector] |
| f6f9a000-f6f9afff r-- 0 1000 [anon:atexit handlers] |
| f6f9b000-f6fbafff r-- 0 20000 /dev/__properties__/properties_serial |
| f6fbb000-f6fbbfff rw- 0 1000 [anon:linker_alloc_vector] |
| f6fbc000-f6fbcfff rw- 0 1000 [anon:linker_alloc_small_objects] |
| f6fbd000-f6fbdfff rw- 0 1000 [anon:linker_alloc_vector] |
| f6fbe000-f6fbffff rw- 0 2000 [anon:linker_alloc] |
| f6fc0000-f6fc0fff r-- 0 1000 [anon:linker_alloc] |
| f6fc1000-f6fc1fff rw- 0 1000 [anon:linker_alloc_lob] |
| f6fc2000-f6fc2fff r-- 0 1000 [anon:linker_alloc] |
| f6fc3000-f6fc3fff rw- 0 1000 [anon:linker_alloc_vector] |
| f6fc4000-f6fc4fff rw- 0 1000 [anon:linker_alloc_small_objects] |
| f6fc5000-f6fc5fff rw- 0 1000 [anon:linker_alloc_vector] |
| f6fc6000-f6fc6fff rw- 0 1000 [anon:linker_alloc_small_objects] |
| f6fc7000-f6fc7fff rw- 0 1000 [anon:arc4random _rsx structure] |
| f6fc8000-f6fc8fff rw- 0 1000 [anon:arc4random _rs structure] |
| f6fc9000-f6fc9fff r-- 0 1000 [anon:atexit handlers] |
| f6fca000-f6fcafff --- 0 1000 [anon:thread signal stack guard page] |
| </pre> |
| |
| <p> |
| 从 Android 5.0 开始,C 库会对其大部分匿名的映射区域进行命名,因此无名区域将会有所减少。 |
| </p> |
| |
| <pre class="devsite-click-to-copy"> |
| f6fcb000-f6fccfff rw- 0 2000 [stack:5081] |
| </pre> |
| |
| <p> |
| 名为 <code>[stack:<var>tid</var>]</code> 的区域是指定线程的堆栈。 |
| </p> |
| |
| <pre class="devsite-click-to-copy"> |
| f6fcd000-f702afff r-x 0 5e000 /system/bin/linker (BuildId: 84f1316198deee0591c8ac7f158f28b7) |
| f702b000-f702cfff r-- 5d000 2000 /system/bin/linker |
| f702d000-f702dfff rw- 5f000 1000 /system/bin/linker |
| f702e000-f702ffff rw- 0 2000 |
| f7030000-f7030fff r-- 0 1000 |
| f7031000-f7032fff rw- 0 2000 |
| ffcd7000-ffcf7fff rw- 0 21000 |
| ffff0000-ffff0fff r-x 0 1000 [vectors] |
| </pre> |
| |
| <p> |
| 您看到的是 <code>[vector]</code> 还是 <code>[vdso]</code> 取决于架构。ARM 使用 <code>[vector]</code>,而所有其他架构均使用 <a href="http://man7.org/linux/man-pages/man7/vdso.7.html" class="external"><code>[vdso]</code></a>。 |
| </p> |
| |
| </body></html> |