巧用寄存器定位android native崩溃问题
背景:
由于android 一些崩溃在libc.so里边一些函数里边,对于问题调查不太方便,本文针对memcpy的一个崩溃,来阐述下此类问题应该如何调查与解决
崩溃堆栈
Bash signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x745fce8000 x0 000000745fc382f0 x1 000000745fce7fc0 x2 ffffffff87a9b060 x3 000000745fcbceb0 x4 00000073e7783070 x5 00000073e7757f60 x6 0000000000000000 x7 0000000000000000 x8 0000000000000000 x9 0000000000000000 x10 0000000000000000 x11 0000000000000000 x12 0000000000000000 x13 0000000000000000 x14 0000a237db54c6d0 x15 0000000000000010 x16 00000071a79deca8 x17 0000007543d1fc80 x18 0000007165a80000 x19 000000733f897810 x20 00000071885ad274 x21 00000071885ad27c x22 00000071885ad278 x23 000000748f9930a0 x24 000000000002b110 x25 0000000087b4ad80 x26 000000745fc382f0 x27 00000071885ae000 x28 0000000000000000 x29 00000071885ad260 sp 00000071885ad1b0 lr 000000717c9d5f30 pc 0000007543d1fc3c backtrace: #00 pc 000000000004ac3c /apex/com.android.runtime/lib64/bionic/libc.so (__memcpy+300) ..............//其他方法的调用栈 |
可以看的出来,崩溃在了__memcpy 这个函数里边,这个是标准库里边的,我们只能review 调用memcpy的地方是否出现问题,首先我们还要区分到底野指针还是越界.
Arm 64 函数调用
通过图中可以看出下来r0---r7 也就是x0-x7, 对应的x0就是memcpy的第一个参数,x1 的第二个参数
void *memcpy(void *__dst, const void *__src, size_t __n);
__dst 对应的x0
__src 对应的x1
__n 对应的x2,对于arm64而言,size_t占用8个字节,因此是x2
为了验证上边是不是如是否正确,接下来写了一段测试代码进行测试
C++ void memcpy_1(size_t a, void *dst, void *src, size_t size) { memcpy(dst, src, size); } int main() { memcpy_1(55,(void*)0x1234, (void*)0x4578, 11); return 0; } |
可以看的出来的,很简单的代码,按照我们的上边猜想memcpy_1(int a, void *dst, void *src, size_t size)的四个参数对应的 x0~x3,下边我就从汇编角度来看下具体
Assembly language 0x100001b40 <+24>: mov x0, #0x37 0x100001b44 <+28>: mov x1, #0x1234 0x100001b48 <+32>: mov x2, #0x4578 0x100001b4c <+36>: mov x3, #0xb -> 0x100001b50 <+40>: bl 0x100001af0 ; memcpy_1 at main.cpp:326 |
看的出来确实如我们预期的一样,那接下来看下memcpy的调用前的汇编
Assembly language test`memcpy_1: -> 0x100001af0 <+0>: sub sp, sp, #0x30;给栈分配了48个字节 0x100001af4 <+4>: stp x29, x30, [sp, #0x20] ;;保存寄存器值加载2个字节到x29,x30 0x100001af8 <+8>: add x29, sp, #0x20 0x100001afc <+12>: stur x0, [x29, #-0x8];x0 临时存放到 [x29, #-0x8] 0x100001b00 <+16>: str x1, [sp, #0x10];x1 临时存放到 [sp, #0x10] 0x100001b04 <+20>: str x2, [sp, #0x8];x2 临时存放到 [sp, #0x8] 0x100001b08 <+24>: str x3, [sp] 0x100001b0c <+28>: ldr x0, [sp, #0x10]; 把[sp, #0x10] 存放到x0 0x100001b10 <+32>: ldr x1, [sp, #0x8]; 把[sp, #0x8] 存放到x1 0x100001b14 <+36>: ldr x2, [sp]; 把[sp] 存放到,放到x2中 0x100001b18 <+40>: bl 0x100003c48 ; symbol stub for: memcpy |
汇编语言可以看出来它最终传递给memcpy的寄存器还是x0,x1,x2,这也满足了我们预期,那你可能会有疑问,memcpy 调用完回来,数据是怎么恢复的,那我们接下来看下对应的汇编
Assembly language 0x100001b1c <+52>: ldp x29, x30, [sp, #0x20]; 与上边相对 恢复现场stp x29, x30, [sp, #0x20] ;;加载2个字节到x29,x30 0x100001b20 <+56>: add sp, sp, #0x30; 把栈分配的48个字节跟减除,最终达到动态栈平衡 |
总结:
同过上边分析,我们明确的只知道x0,x1,x2对应的memcpy的3个参数,根据上边崩溃堆栈的x0,x1, x2的值如下:
x0 000000745fc382f0 x1 000000745fce7fc0 x2 ffffffff87a9b060
也就是
void *memcpy(void *__dst, const void *__src, size_t __n);
__dst = 000000745fc382f0
__src = 000000745fce7fc0
__n = ffffffff87a9b060
可以看的出来 拷贝字节长度__n 明显ffffffff87a9b060,这个值明显有问题,是个负数,所以我们要想解决这个崩溃,就要知道这个负值是怎么来的,可以在适当的地方加一些保护。