系统开发面试你会这个native crash的面试题吗?
文章目录
- 背景:
- Abort中止
- Null 指针空指针异常
- 低地址空指针异常 Null 指针(容易识别为非空指针)
- FORTIFY 失败
- -fstack-protector 检测到的堆栈损坏
- 来自不允许的系统调用的 Seccomp SIGSYS
背景:
近期有学员参加fw相关的面试,回来都和马哥报喜说表现挺好的也通过了技术面,面试官都很满意,也给马哥带回来了一些面试题,也有偶尔一两个是回答的没有非常有底气的,比如关于native crash的一个面试题目:
经常在做native开发时候会有native crash,请问平常开发中遇过哪几种经典native crash请分别列举说出对应情况。
下面针对这个native crash的面试题目,马哥这边收集了几个经典的案例来给大家分享,比较常见有Abort类型,纯空指针类型,低地址空指针类型等.
Abort中止
中止操作很有趣,因为这是刻意而为。执行中止操作可通过多种不同的方法(包括调用 abort(3)、使 assert(3) 失败、使用 Android 特有的严重记录类型之一)来实现,但所有这些方法都涉及到调用 abort。abort 调用会向发起调用的线程发出 SIGABRT 信号,因此为了识别这种情况,您需要在 debuggerd 输出中查找以下两项内容:libc.so 中显示“abort”的帧,以及 SIGABRT 信号。
您可能会看到明确的“中止消息”行。不过,您还应该查看 logcat 输出,了解此线程在刻意终止自身之前所记录的内容,因为与 assert(3) 或高级别的严重记录设备不同的是,abort(3) 不接受任何消息。
当前版本的 Android 内嵌了 tgkill(2) 系统调用,因此它们的堆栈最容易读取,同时对 abort(3) 的调用位于最顶端:
pid:4637, tid:4637, name: crasher>>>crasher<<<signal6(SIGABRT), code-6(SI_TKILL), fault addr -------- Abort message:'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 00000000ipffb44c20 sp ffb44a08 lr eace2b0b pc eace2b16 backtrace:#00 pc 0001cb16 /system/lib/libc.so (abort+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)在原始中止调用(此处为帧 4)与实际发送信号(此处为帧 0)之间,较低版本的 Android 需要遵循复杂的路径。特别是在 32 位 ARM 上运行的 Android,它会将 __libc_android_abort(此处为帧 3)添加到其他平台的 raise/pthread_kill/tgkill 序列:
pid:1656, tid:1656, name: crasher>>>crasher<<<signal6(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 ff96ae1cip00000006 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您可以使用 crasher abort 重现此类崩溃问题的实例。Null 指针空指针异常
这是典型的原生代码崩溃问题,虽然它只是下一类崩溃问题的特殊情况,但值得单独说明,因为这类崩溃问题通常无需细细思量。
在以下示例中,尽管崩溃函数在 libc.so 内,但由于字符串函数仅在指定给它们的指针处进行操作,因此您可以推断出在调用 strlen(3) 时指定的是 Null 指针;对于这类崩溃问题,应直接找发起调用的代码的作者加以解决。在这种情况下,帧 #01 是不良调用程序。
pid:25326, tid:25326, name: crasher>>>crasher<<<signal11(SIGSEGV), code1(SEGV_MAPERR), fault addr 0x0 r0 00000000 r1 00000000 r2 00004c00 r3 00000000 r4 ab088071 r5 fff92b34 r6 00000002 r7 fff92b40 r8 00000000 r9 00000000 sl 00000000 fp fff92b2cipab08cfc4 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)您可以使用 crasher strlen-NULL 重现此类崩溃问题的实例。
低地址空指针异常 Null 指针(容易识别为非空指针)
在许多情况下,故障地址不会为 0,而是其他一些小数字。两位或三位地址尤其常见,而六位地址几乎肯定不是 Null 指针解引用(它需要 1MiB 的偏移量)。通常,当您的代码将 Null 指针解引用为看似有效的结构时,就会出现这种情况。常用的函数是 fprintf(3)(或任何其他使用 FILE* 的函数)和 readdir(3),因为代码通常无法检查到底是 fopen(3) 调用先成功,还是 opendir(3) 调用先成功。
下面是一个 readdir 示例:
pid:25405, tid:25405, name: crasher>>>crasher<<<signal11(SIGSEGV), code1(SEGV_MAPERR), fault addr 0xc r0 0000000c r1 00000000 r2 00000000 r3 3d5f0000 r4 00000000 r5 0000000c r6 00000002 r7 ff8618f0 r8 00000000 r9 00000000 sl 00000000 fp ff8618dcipedaa6834 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)在此示例中,导致崩溃问题的直接原因是 pthread_mutex_lock(3) 曾尝试访问地址 0xc(第 0 帧)。但是 pthread_mutex_lock 执行的第一项操作是解引用指定给它的 pthread_mutex_t* 的 state 元素。如果您查看源代码,会发现该元素在结构中的偏移量为零,这表示指定给 pthread_mutex_lock 的指针 0xc 无效。从帧 1 可以看出,readdir 会将该指针指定给它,这会从指定的 DIR* 中提取 mutex_ 字段。通过查看该结构,您会发现 struct DIR 中 mutex_ 的偏移量为 sizeof(int) + sizeof(size_t) + sizeof(dirent*),在 32 位设备上表示为 4 + 4 + 4 = 12 = 0xc,这样便可以找到错误所在:调用程序向 readdir 传递了一个 Null 指针。此时,您可以将该堆栈粘贴到堆栈工具中,以找出这个问题在 logcat 中的发生位置。
structDIR{intfd_;size_t available_bytes_;dirent*next_;pthread_mutex_t mutex_;dirent buff_[15];longcurrent_pos_;};其实在大多数情况下,您可以跳过此分析。一个充分的低位故障地址通常意味着您可以跳过堆栈中的任意 libc.so 帧,并直接归咎于发起调用的代码。不过,情况并非总是如此,这些例外将是您用作展示的绝佳机会。
您可以使用 crasher fprintf-NULL 或 crasher readdir-NULL 重现此类崩溃问题的实例。
FORTIFY 失败
FORTIFY 失败是中止的一种特殊情况,当 C 库检测到可能导致安全漏洞的问题时,就会发生 FORTIFY 失败。很多 C 库函数已得到加强;它们需要一个额外的参数来确定缓冲区的实际大小,并在运行时检查您尝试执行的操作是否真的合理。以下示例显示代码尝试使用 read(fd, buf, 32) 读入实际上只有 10 字节长的缓冲区…
pid:25579, tid:25579, name: crasher>>>crasher<<<signal6(SIGABRT), code-6(SI_TKILL), fault addr -------- Abort message:'FORTIFY: read: prevented 32-byte write into 10-byte buffer'r0 00000000 r1 000063eb r2 00000006 r3 00000008 r4 ff96f350 r5 000063eb r6 000063eb r7 0000010c r8 00000000 r9 00000000 sl 00000000 fp ff96f49cip00000000 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 (__fortify_fatal+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)您可以使用 crasher fortify 重现此类崩溃问题的实例。
-fstack-protector 检测到的堆栈损坏
编译器的 -fstack-protector 选项会在具有栈上缓冲区的函数中插入检查机制,以防止缓冲区溢出。默认情况下,系统会为平台代码(而非应用)启用此选项。启用此选项后,编译器会向函数序言添加指令,以在堆栈上写入刚刚超过上一局部值的随机值,并向函数结尾添加指令以进行回读并确认是否发生更改。如果该值已更改,则表示该值已被缓冲区溢出覆盖,因此该结尾会调用 __stack_chk_fail 来记录消息和中止。
pid:26717, tid:26717, name: crasher>>>crasher<<<signal6(SIGABRT), code-6(SI_TKILL), fault addr -------- Abort message:'stack corruption detected'r0 00000000 r1 0000685d r2 00000006 r3 00000008 r4 ffd516d8 r5 0000685d r6 0000685d r7 0000010c r8 00000000 r9 00000000 sl 00000000 fp ffd518bcip00000000 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 (__stack_chk_fail+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)您可以根据回溯中是否出现 __stack_chk_fail 以及特定中止消息将此中止与其他类型的中止区分开来。
您可以使用 crasher smash-stack 重现此类崩溃问题的实例。
来自不允许的系统调用的 Seccomp SIGSYS
seccomp 系统(具体是指 seccomp-bpf)会限制对系统调用的访问权限。如需详细了解适用于平台开发者的 seccomp,请参阅博文 Android O 中的 Seccomp 过滤器。调用受限系统调用的线程将收到信号 SIGSYS 及代码 SYS_SECCOMP。系统调用编号将与架构一起显示在原因行中。需要注意的是,系统调用编号会因架构而异。例如,readlinkat(2) 系统调用在 x86 系统上的编号为 305,而在 x86-64 系统上的编号则为 267。在 arm 和 arm64 平台上,调用编号也不一样。因为系统调用编号因架构而异,所以在通常情况下,使用堆栈轨迹来找出不允许的系统调用比在标头中寻找系统调用编号更容易。
pid:11046, tid:11046, name: crasher>>>crasher<<<signal31(SIGSYS), code1(SYS_SECCOMP), fault addr -------- Cause: seccomp prevented call to disallowed arm system call99999r0 cfda0444 r1 00000014 r240000000r3 00000000 r4 00000000 r5 00000000 r6 00000000 r7 0001869f r8 00000000 r9 00000000 sl 00000000 fp fffefa58ipfffef898 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)您可以根据信号行中是否出现 SYS_SECCOMP 以及原因行中的说明,将不允许的系统调用与其他崩溃问题区分开来。
上面native crash只列举最常见的几种,不一定最全面,欢迎各位粉丝朋友进行留言补充。
原文地址:
https://mp.weixin.qq.com/s/IJU_oWFhW4ARtnKCu4arAg
更多framework实战开发案例获取,请关注下面“千里马学框架”dd马哥
