上节对APCS的基本数据类型做了分类和对通用寄存器X0 ~ X30做了标准的说明。
SIMD和浮点寄存器组
现在对SIMD和浮点寄存器组做一个说明。
寄存器 | 特殊命名 | 在函数调用过程中的作用 | 备注 |
V16 ~ X31 | 临时寄存器, 若需要,调用者需要保存数据 | ||
V8 ~ V15 | 临时寄存器,被调用者需要保存的寄存器 (只需要保存低64位,如果保存高位,需要调用者保存) | ||
V0 ~ V7 | 参数传递 / 返回寄存器 | 被用来传入参数或传出结果,也可用来保存函数中数据 |
栈
APCS标准规定,如果不能完全通过寄存器来完成参数的传递,那么,参数的传递也可以通过栈来进行传递。
参数传递规则定义
参数传递分为两种不同的函数,1. 可变参数函数 2. 非可变参数函数
1: 可变参数函数。 对于可变参数函数来说,调用者是可以完全确定参数传入的数量,类型的。而对于被调用者来说,只能确定参数最小集合。并根据这个最小结合的参数动态确定传入的参数数量和类型。其中,可以确定的参数被称为命名参数(Named Arguments),而不能确定的参数被称为匿名参数(Anonymous Arguments)。
2: 非可变参数函数。 非可变参数函数,可以认为是没有匿名参数的可变参数函数。
针对代码中的不同情况,APCS提供了针对不同情况的规则定义,其定义如下:
1: 对于无法静态获得大小的组合类型,数据拷贝到内存,参数替代为内存地址指针进行传递
2: 在向量运算中,同类型向量做无修改传递
3: 对于大于16字节的组合类型,调用者负责将数据拷贝到内存,并将内存地址指针作为参数传递
4: 对于组合类型,应以8字节对齐方式保存
5: 对所有浮点类型,不论大小,在8个以内,每个参数使用一个V0 ~ V7中的寄存器按传入顺序进行传递。
6: 向量运算中,在8个以内, 每个同类型向量使用一个V0 ~ V7寄存器按传入顺序进行传递
7: 向量运算中,同类型向量以8字节对齐
8: 若参数是同类型向量,四精度浮点和短向量类型, 则对齐方式以8字节或参数类型的自然对齐数中大的为对齐字节数
9: 若参数是半浮点,单浮点类型,则参数大小扩展为8字节。拷贝至寄存器中时,扩展比特的值为未指定值。
10: 若参数是同类型向量,半浮点,单浮点,四浮点或短向量类型, 则通过内存进行传递参数时,向量被8字节对齐的复制到内存地址中,参数传递区内存容量扩展,并进行传递。
11: 若参数是整型或指针类型等小于等于8字节的数据,在8个以内,每个参数使用一个参数传递寄存器(X0 ~ X7)按传入顺序进行传递
12: 若参数类型是整型且自然对齐是16字节,且目前未被占用的参数传递寄存器还大于2个,则参数扩展到下两个参数传递寄存器(X0 ~ X7)进行传递,前一个寄存器保存低地址位,后一个保存高地址位。
13: 若参数是组合类型,且参数大小在8个字节以内,且目前未被占用的参数传递寄存器还大于1个, 则参数被放置到一个参数传递寄存器(X0 ~ X7)进行传递。就像它们被从一个8字节对齐的内存地址中通过ldr指令加载到寄存器中一样。
14: 通用寄存器进行参数传递最大只有8个。从X0 ~ X7
15: 通过内存地址进行参数传递的,参数是8字节对齐。
16: 如果参数是组合类型,则参数被复制到内存中以8字节对齐方式保存。
17: 如果参数不足8字节,则参数被扩展到8字节通过寄存器操作。扩展为内容未知。
18: 如果参数通过内存传递,则参数传递区内存使用容量随参数递增,参数保存方式以内存对齐方式保存
举例
对于上述这些标准内容,我们单看条目,可能很难有具体的理解,接下来我们以一个tombstone为例来说明一下:
05-06 13:06:40.576 9694 29707 F libc : Fatal signal 11 (SIGSEGV), code 1, fault addr 0x0 in tid 29707 (pool-16-thread-)
05-06 13:06:40.628 500 500 F DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
05-06 13:06:40.629 500 500 F DEBUG : Build fingerprint: 'XXX/XXX/XXX:6.0.1/MMB29M/1.8.119_160503:user/release-keys'
05-06 13:06:40.629 500 500 F DEBUG : Revision: '0'
05-06 13:06:40.629 500 500 F DEBUG : ABI: 'arm64'
05-06 13:06:40.629 500 500 F DEBUG : pid: 9694, tid: 29707, name: pool-16-thread- >>> com.xxx.mms <<<
05-06 13:06:40.629 500 500 F DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
05-06 13:06:40.661 500 500 F DEBUG : x0 0000000000000000 x1 000000000000000f x2 0000000000000fde x3 0000000000000000
05-06 13:06:40.661 500 500 F DEBUG : x4 0000007f5fe389c8 x5 0000000000000fcf x6 00000000000000fc x7 00000000000000fd
05-06 13:06:40.661 500 500 F DEBUG : x8 0000000000000fd0 x9 0000007f5fe39998 x10 00000000000000fd x11 0000007f5fe39998
05-06 13:06:40.661 500 500 F DEBUG : x12 0000000000000038 x13 0000000000000005 x14 0000000000000005 x15 0000000000000005
05-06 13:06:40.661 500 500 F DEBUG : x16 0000007f62eb3d60 x17 0000007f7eec8414 x18 0000000000000005 x19 0000007f5fe399a8
05-06 13:06:40.661 500 500 F DEBUG : x20 0000000000000000 x21 0000007f5fe38610 x22 0000007f62eb4000 x23 0000007f5fe3eb6c
05-06 13:06:40.661 500 500 F DEBUG : x24 0000000000000000 x25 0000007f5fe389b0 x26 0000007f5fe38278 x27 0000007f5fe38270
05-06 13:06:40.661 500 500 F DEBUG : x28 0000007f5fe38278 x29 0000007f5fe40118 x30 0000007f62e93f88
05-06 13:06:40.661 500 500 F DEBUG : sp 0000007f5fe381c0 pc 0000007f62e93fe4 pstate 0000000020000000
05-06 13:06:40.666 500 500 F DEBUG :
05-06 13:06:40.666 500 500 F DEBUG : backtrace:
05-06 13:06:40.666 500 500 F DEBUG : #00 pc 0000000000015fe4 /system/priv-app/XMms/XMms.apk (offset 0xf64000)
05-06 13:06:40.666 500 500 F DEBUG : #01 pc 0000000000011e94 /system/priv-app/XMms/XMms.apk (offset 0xf64000)
05-06 13:06:40.666 500 500 F DEBUG : #02 pc 0000000000016eec /system/priv-app/XMms/XMms.apk (offset 0xf64000)
05-06 13:06:40.666 500 500 F DEBUG : #03 pc 0000000000007da8 /system/priv-app/XMms/XMms.apk (offset 0xf64000)
05-06 13:06:40.666 500 500 F DEBUG : #04 pc 0000000000b8aa14 /system/priv-app/XMms/oat/arm64/XMms.odex (offset 0x75d000)
05-06 13:06:41.406 500 500 F DEBUG :
05-06 13:06:41.406 500 500 F DEBUG : Tombstone written to: /data/tombstones/tombstone_01
单从上面看,异常错误是一个signal 11的段地址错误。而且显示fault addr 是0x0,这说明是发生了一个空指针的错误。
15fd8: 6b18001f cmp w0, w24
15fdc: 54000acd b.le 16134 <calcWords+0x26c>
15fe0: f947b2c0 ldr x0, [x22,#3936]
15fe4: f8746800 ldr x0, [x0,x20]
我们追到15fe4这个地址,很明显的可以看到,“ ldr x0, [x0,x20] ” 这样的取地址指令,这个时候,我们就可以猜想,按照ARM的APCS64的标准, X0寄存器是上一级调用者调用到当前函数传入的第一个非浮点参数(也可能就是第一个参数,取决于参数传入顺序)。普通情况下,如果这个寄存器没有被复写重用,那么,发生异常的情况,就很有可能是传入的第一个非浮点参数出现了问题(有可能传入了一个空指针)。而且,从这里的汇编指令也可以看到,X0确实是被当作一个指针地址来用的。这样,我们就可以排查代码中对应的变量是否存在被赋值为空地址的问题了。