死机重启问题中,有部分是访问了已释放的内存导致,这就是典型的use after free问题.
打开CONFIG_SLUB_DEBUG和CONFIG_SLUB_DEBUG_ON宏开关后,系统就可以监测内存的释放与分配调用栈.
1. slab 内存布局
slub的内存管理原理这里就不在详述.直接给出slabobject对象的内存布局,object内存包含下面四个部分:
object_size +Redzone + Freepointer +2*track+pading
object_size :待分配内存的大小,alloc时为0x5a,free后为0x6b,但是最后一个Byte为0xa5表示object的结束,之后的数据都为metadata.
Redzone : 标记区.alloc填充0xcc,free后填充0xbb
Freepointer :指向下一个空闲的object
2个structtrack结构,用于跟踪内存的分配与释放栈
pading :填充区,为了内存对齐,填充为0x5a
下表为典型的object内存布局
2. 相关变量
kmem_cache->inuse= object_size + Redzone
kmem_cache->offset= inuse or =0
kmem_cache->size= object_size +Redzone+Freepointer+2*track+pading
3. 查找track信息
当系统出现KE时,用gdb调试打印内存,发现访问的内存全变成了0x6b,则可以怀疑是userafter free。
这里以skb为例,假如skb->protocol开始的内存全变成0x6b,获取protocol的地址
(gdb) p&(skb->protocol)
$6 = (__be16 *)0xffffffc02a3020c0
输出从0xffffffc02a3020c0地址开始的512Byte内容
(gdb) x /512xb0xffffffc02a3020c0
0xffffffc02a3020c0: 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b
0xffffffc02a3020c8: 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b
0xffffffc02a3020d0: 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b
0xffffffc02a3020d8: 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b
0xffffffc02a3020e0: 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b 0xa5
0xffffffc02a3020e8: 0xbb 0xbb 0xbb 0xbb 0xbb 0xbb 0xbb 0xbb
0xffffffc02a3020f0: 0x80 0x21 0x30 0x2a 0xc0 0xff 0xff 0xff
0xffffffc02a3020f8: 0x90 0xdb 0xa8 0x00 0xc0 0xff 0xff 0xff
0xffffffc02a302100: 0x9c 0x96 0x1e 0x04 0x58 0x9d 0x1e 0x04
0xffffffc02a302108: 0x3c 0xb0 0x1e 0x04 0x8c 0xdb 0xa8 0x04
0xffffffc02a302110: 0x28 0x0f 0xba 0x04 0x8c 0x36 0xba 0x04
0xffffffc02a302118: 0xb8 0x37 0xba 0x04 0x6c 0x16 0xba 0x04
0xffffffc02a302120: 0x00 0x00 0x00 0x00 0x11 0x55 0x00 0x00
0xffffffc02a302128: 0xc4 0x29 0x4e 0x01 0x01 0x00 0x00 0x00
0xffffffc02a302130:0x04 0xce 0xa8 0x00 0xc0 0xff 0xff 0xff
0xffffffc02a302138: 0x14 0xa0 0x1e 0x04 0xb8 0xa3 0x1e 0x04
0xffffffc02a302140: 0x6c 0xb8 0x1e 0x04 0x00 0xce 0xa8 0x04
0xffffffc02a302148: 0xb4 0xe3 0xa8 0x04 0x38 0x53 0xa9 0x04
0xffffffc02a302150: 0xb0 0x96 0xb8 0x04 0x3c 0x33 0xa8 0x04
0xffffffc02a302158: 0x03 0x00 0x00 0x00 0x29 0x01 0x00 0x00
0xffffffc02a302160: 0xc2 0x29 0x4e 0x01 0x01 0x00 0x00 0x00
0xffffffc02a302168: 0x5a 0x5a 0x5a 0x5a 0x5a 0x5a 0x5a 0x5a
0xffffffc02a302170: 0x5a 0x5a 0x5a 0x5a 0x5a 0x5a 0x5a 0x5a
0xffffffc02a302178: 0x5a 0x5a 0x5a 0x5a 0x5a 0x5a 0x5a 0x5a
0xffffffc02a302180: 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b
0xffffffc02a302188: 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b
0xffffffc02a302190: 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b
…...............................................................省去部分内存..................................................................
0xffffffc02a302258: 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b
0xffffffc02a302260: 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b 0x6b 0xa5
0xffffffc02a302268: 0xbb 0xbb 0xbb 0xbb 0xbb 0xbb 0xbb 0xbb
0xffffffc02a302270: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xffffffc02a302278: 0x90 0xdb 0xa8 0x00 0xc0 0xff 0xff 0xff
0xffffffc02a302280: 0x9c 0x96 0x1e 0x04 0x58 0x9d 0x1e 0x04
0xffffffc02a302288: 0x3c 0xb0 0x1e 0x04 0x8c 0xdb 0xa8 0x04
0xffffffc02a302290: 0xcc 0x43 0xa9 0x04 0xcc 0xa3 0xa8 0x04
0xffffffc02a302298: 0x68 0x9e 0xb8 0x04 0x44 0x32 0xa8 0x04
0xffffffc02a3022a0: 0x03 0x00 0x00 0x00 0xeb 0x02 0x00 0x00
0xffffffc02a3022a8: 0xc2 0x29 0x4e 0x01 0x01 0x00 0x00 0x00
0xffffffc02a3022b0: 0x04 0xce 0xa8 0x00 0xc0 0xff 0xff 0xff
0xffffffc02a3022b8: 0x14 0xa0 0x1e 0x04 0xb8 0xa3 0x1e 0x04
从内存中查找关键字0xa5,0xbb,再参考slabobject的内存布局,很容易找到alloctrack和freetrack的地址,再用gdb打印出内存
free:0xffffffc02a302130
p /x *((struct track*)0xffffffc02a302130)
$13 = {addr =0xffffffc000a8ce04, addrs = {0x41ea014, 0x41ea3b8, 0x41eb86c,0x4a8ce00, 0x4a8e3b4, 0x4a95338, 0x4b896b0, 0x4a8333c}, cpu = 0x3,pid = 0x129,
when =0x1014e29c2}
同理,也可以打印出alloctrack的内存。
4. 转换track信息
track 的定义
struct track {
unsigned longaddr; /* Called from address */
#ifdefCONFIG_STACKTRACE
u32addrs[TRACK_ADDRS_COUNT]; //函数调用栈信息
/* we store theoffset after MODULES_VADDR for kernel module and kernel text address */
#endif
int cpu; /* Wasrunning on cpu */
int pid; /* Pidcontext */
unsigned longwhen; /* When did the operation occur */
};
为了节省空间,存储地址时,去掉了内核code起始地址,也就是需要addrs的值加上MODULES_VADDR,(MODULES_VADDR+t->addrs[i]),在这里MODULES_VADDR=0xffffffbffc000000,最后通过addr2line打印出free的调用栈为:
kernel-3.18/net/socket.c:791
kernel-3.18/net/unix/af_unix.c:1914
kernel-3.18/net/core/datagram.c:243
/kernel-3.18/net/core/skbuff.c:630
kernel-3.18/net/core/skbuff.c:561
kernel-3.18/mm/slub.c:2923
另外,需要说明MODULES_VADDR的值,每个平台都可能不一样,所有要根据实际情况取值.