一次__libc_message的排查
Posted 安庆
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一次__libc_message的排查相关的知识,希望对你有一定的参考价值。
abort的堆栈如下:
#0 0x00007f338dd60b55 in raise () from /lib64/libc.so.6
#1 0x00007f338dd620c5 in abort () from /lib64/libc.so.6
#2 0x00007f338dd9ee0f in __libc_message () from /lib64/libc.so.6
#3 0x00007f338dda4628 in malloc_printerr () from /lib64/libc.so.6
#4 0x000000000046abfe in OSMemory::Delete (inMemory=0x7f333e7fcf20) at OSMemory.cpp:278
#5 0x000000000046ac2f in operator delete (mem=0x7f333e7fcf20) at OSMemory.cpp:202
#6 0x000000000040e8a7 in __gnu_cxx::new_allocator<std::_List_node<CZMBuff*> >::deallocate (this=0x7f32a4a155a0, __p=0x7f333e7fcf20) at /usr/include/c++/4.3/ext/new_allocator.h:98
#7 0x000000000040e8cf in std::_List_base<CZMBuff*, std::allocator<CZMBuff*> >::_M_put_node (this=0x7f32a4a155a0, __p=0x7f333e7fcf20) at /usr/include/c++/4.3/bits/stl_list.h:318
#8 0x000000000040e9ef in std::_List_base<CZMBuff*, std::allocator<CZMBuff*> >::_M_clear (this=0x7f32a4a155a0) at /usr/include/c++/4.3/bits/list.tcc:79
#9 0x000000000049d579 in std::list<CZMBuff*, std::allocator<CZMBuff*> >::clear (this=0x7f32a4a155a0) at /usr/include/c++/4.3/bits/stl_list.h:1066
由于该段堆栈处于对象的销毁过程,所以应该是free的报错。根据对象本身的内存池设计,在malloc的时候,我们使用用户态的一个记录结构,记录了对象的长度。结构如下:
typedef struct
{
size_t ID;
size_t size;
}mem_hdr;
两个都是8位的长度,之后再跟实际的数据,也就是我调用my_malloc的时候,如果是传入24个字节,那么最终会向glibc的malloc提交40个字节,24+16.
查看free的异常的数据如下:
x /40xg 0x7f333e7fcf20 -64 0x7f333e7fcf20 就是上面堆栈中inMemory的值,这个值真正传给glibc的时候,会减去16而提交,即为0x7f333e7fcf0x7f333e7fcee0: 0x0000000000000000 0x0000000000000028
0x7f333e7fcef0: 0xffffffffffffffff 0xffffffffffffffff---------------------------------------这两列值明显异常,按道理应该是指针
0x7f333e7fcf00: 0xffffffffffffffff 0x00000000ffffffff--------------------------------
0x7f333e7fcf10: 0x0000000000000000 0x0000000000000028
0x7f333e7fcf20: 0x00007f32a57976e0 0x00007f333f7c08e0
0x7f333e7fcf30: 0x00007f32c25b2618 0x0000000000000035-------------这个转化为二进制就是110101 ,后面三位代表flag,#define PREV_INUSE 0x1,前面那个110000为48,表示长度
0x7f333e7fcf40: 0x0000000000000000 0x0000000000000028
0x7f333e7fcf50: 0x00007f330047a640 0x00007f333dbebfd0
0x7f333e7fcf60: 0x00007f32b04b81b8
这个就是应用程序的mem_hdr结构的id 和size,40转换成16进制就是0x28,0x28后面24个字节(3个指针)也应该
是用户数据,在本例中,分别就是 _List_node_base* _M_next; _List_node_base* _M_prev; _Tp _M_data; // 数据域,即标准模板类的管理结构。
正常的例子如下:
0x7f333e7fcf10: 0x0000000000000000 0x0000000000000028
0x7f333e7fcf20: 0x00007f32a57976e0 0x00007f333f7c08e0
0x7f333e7fcf30: 0x00007f32c25b2618 0x0000000000000035--------------最关键的是0x0000000000000035值被踩成了0x00000000ffffffff,如果只踩24字节而不是32字节,就不会glibc中报错了。
0x7f333e7fcf40: 0x0000000000000000 0x0000000000000028--------------下一个结构开始
分为两段来看,下面那段是正常的分配,上面那段是异常的分配,可以明显看出,上面0x1497650地址开始那段的32个字节,是有问题的。
我们回一下malloc的内存分配管理单元结构:
struct malloc_chunk { INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */ INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */ struct malloc_chunk* fd; /* double links -- used only if free. */ struct malloc_chunk* bk; /* Only used for large blocks: pointer to next larger size. */ struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */ struct malloc_chunk* bk_nextsize; };
prev_size: If the previous chunk is free, this field contains the size of previous chunk. Else if previous chunk is allocated, this field contains previous chunk’s user data.
size: This field contains the size of this allocated chunk. Last 3 bits of this field contains flag information.
- PREV_INUSE (P) – This bit is set when previous chunk is allocated.
- IS_MMAPPED (M) – This bit is set when chunk is mmap’d.
- NON_MAIN_ARENA (N) – This bit is set when this chunk belongs to a thread arena.
Bins: Bins are the freelist datastructures. They are used to hold free chunks. Based on chunk sizes, different bins are available:
- Fast bin
- Unsorted bin
- Small bin
- Large bin
映射到内存示意图上如下图所示:
可以看到,我们每次malloc返回的指针并不是内存块的首指针,前面还有两个size_t大小的参数,对于非空闲内存而言size参数最为重要。size参数存放着整个chunk的大小,由于物理内存的分配是要做字节对齐的,所以size参数的低位用不上,便作为flag使用。
size被写坏,有两种结果。一种是free函数能检查出这个错误,程序就会先输出一些错误信息然后abort;一种是free函数无法检查出这个错误,程序便往往会直接crash。
根据最上面的堆栈推测,诱发bug的是前一种情况。
根据多个core文件的规律,发现每次踩的都是32字节,且踩的数据一模一样,都是:
0x1497650: 0xffffffffffffffff 0xffffffffffffffff
0x1497660: 0xffffffffffffffff 0x00000000ffffffff
换算成实际代码,有两种可能,一种是赋值为-1,一种是直接memcpy的时候是0xffffffffffffffff 。
切换到对应的堆栈,使用info register看寄存器,获取出来的CZMBuff是ok的,由于free的时候,是从标准模板类的双向循环列表中移除某个节点,
移除之后,调用free来释放对应的循环链表管理结构,此时出了问题。
标准模板类中的循环列表的结构,表示如下:
// ListNodeBase定义 struct _List_node_base { _List_node_base* _M_next; _List_node_base* _M_prev; }; // ListNode定义 template <class _Tp> struct _List_node : public _List_node_base { _Tp _M_data; // 数据域 };
我们的数据域,其实是一个指向CZMBuff的二级指针,因为直接使用p不好打印链表中的内容,所以需要借助脚本:
创建一个脚本文件,里面包含如下内容(可以在网上下载:)
define plist if $argc == 0 help plist else set $head = &$arg0._M_impl._M_node set $current = $arg0._M_impl._M_node._M_next set $size = 0 while $current != $head if $argc == 2 printf "elem[%u]: ", $size p *($arg1*)($current + 1) end if $argc == 3 if $size == $arg2 printf "elem[%u]: ", $size p *($arg1*)($current + 1) end end set $current = $current._M_next set $size++ end printf "List size = %u \n", $size if $argc == 1 printf "List " whatis $arg0 printf "Use plist <variable_name> <element_type> to see the elements in the list.\n" end end end document plist Prints std::list<T> information. Syntax: plist <list> <T> <idx>: Prints list size, if T defined all elements or just element at idx Examples: plist l - prints list size and definition plist l int - prints all elements and list size plist l int 2 - prints the third element in the list (if exists) and list size end define plist_member if $argc == 0 help plist_member else set $head = &$arg0._M_impl._M_node set $current = $arg0._M_impl._M_node._M_next set $size = 0 while $current != $head if $argc == 3 printf "elem[%u]: ", $size p (*($arg1*)($current + 1)).$arg2 end if $argc == 4 if $size == $arg3 printf "elem[%u]: ", $size p (*($arg1*)($current + 1)).$arg2 end end set $current = $current._M_next set $size++ end printf "List size = %u \n", $size if $argc == 1 printf "List " whatis $arg0 printf "Use plist_member <variable_name> <element_type> <member> to see the elements in the list.\n" end end end document plist_member Prints std::list<T> information. Syntax: plist <list> <T> <idx>: Prints list size, if T defined all elements or just element at idx Examples: plist_member l int member - prints all elements and list size plist_member l int member 2 - prints the third element in the list (if exists) and list size end
然后使用plist方法和plist_member 来获取成员的值,
plist this->m_listBuff
List size = 16595
其中引用计数为counter ,
counter =1 个数为204
counter = 0 个数为 16596
两者相加为16800,但是 list 里面,只有 16595 个元素,少掉的那个元素去哪了?没有进入链表唯一的可能是,链表中
以上是关于一次__libc_message的排查的主要内容,如果未能解决你的问题,请参考以下文章
[AndroidStudio]_[初级]_[配置自动完成的代码片段]
[AndroidStudio]_[初级]_[配置自动完成的代码片段]