一次__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的排查的更多相关文章
- Tomcat shutdown执行后无法退出进程问题排查及解决
问题定位及排查 上周无意中调试程序在Linux上ps -ef|grep tomcat发现有许多tomcat的进程,当时因为没有影响系统运行就没当回事.而且我内心总觉得这可能是tomcat像nginx一 ...
- myrocks复制中断问题排查
背景 mysql可以支持多种不同的存储引擎,innodb由于其高效的读写性能,并且支持事务特性,使得它成为mysql存储引擎的代名词,使用非常广泛.随着SSD逐渐普及,硬件存储成本越来越高,面向写优化 ...
- Java线上应用故障排查之一:高CPU占用
一个应用占用CPU很高,除了确实是计算密集型应用之外,通常原因都是出现了死循环. 以我们最近出现的一个实际故障为例,介绍怎么定位和解决这类问题. 根据top命令,发现PID为28555的Java进程占 ...
- wordpress插件bug排查后记(记一次由于开启memecached引起的插件bug)
这篇文章是写给自己的. 周三的时候我在维护公司的一个wordpress项目页面时发现了一个非常奇怪的情况:当我尝试更新网站上的一个页面后,在wordpress后台的编辑器中发现其内容并没有按我预期的将 ...
- [AlwaysOn Availability Groups]AG排查和监控指南
AG排查和监控指南 1. 排查场景 如下表包含了常用排查的场景.根据被分为几个场景类型,比如Configuration,client connectivity,failover和performance ...
- mysql半同步复制问题排查
1.问题背景 默认情况下,线上的mysql复制都是异步复制,因此在极端情况下,主备切换时,会有一定的概率备库比主库数据少,因此切换后,我们会通过工具进行回滚回补,确保数据不丢失.半同步复制则 ...
- 数据库实战案例—————记一次TempDB暴增的问题排查
前言 很多时候数据库的TempDB.日志等文件的暴增可能导致磁盘空间被占满,如果日常配置不到位,往往会导致数据库故障,业务被迫中断. 这种文件暴增很难排查,经验不足的一些运维人员可能更是无法排查具体原 ...
- 一次xbuild编译失败的排查
今天一个待上线服务测试完毕,需要构建CI,按照模板配置好包还原,xbuild编译,报错,错误信息如下: EtcdRegister.cs(8,15): error CS0234: The type or ...
- 一次kibana服务失败的排查过程
公司在kubernetes集群上稳定运行数月的kibana服务于昨天下午突然无法正常提供服务,访问kibana地址后提示如下信息: 排查过程: 看到提示后,第一反应肯定是检查elasticsearch ...
随机推荐
- Python的类与类型
1.经典类与新式类 在了解Python的类与类型前,需要对Python的经典类(classic classes)与新式类(new-style classes)有个简单的概念. 在Python 2.x及 ...
- C# log4net 的配置
项目的日志组件是必备可少的,任何项目中都需要.这样既方便前期的开发测试也方便项目后期的项目维护.C#项目的一个不错的日志组件是log4net,下面我就把桌面应用程序.控制台程序.网站中log4net的 ...
- yield next和yield* next的区别
yield next和yield* next之间到底有什么区别?为什么需要yield* next?经常会有人提出这个问题.虽然我们在代码中会尽量避免使用yield* next以减少新用户的疑惑,但还是 ...
- F# 之旅(下)
写在前面的话 学习 F# 一定要去体会函数式编程的特点,推荐一下阮一峰的日志<函数式编程入门教程>. 在这篇文章中 递归函数 记录和可区分联合类型 模式匹配 可选类型 度量单位 类和接口 ...
- mac 上安装服务,查看服务,重启和关闭
首先了解下的Mac的 homebrew ,官网:https://brew.sh/index_zh-cn.html 简单的说: Homebrew 能干什么? 答:使用 Homebrew 安装 Apple ...
- .net的retrofit--WebApiClient库深入篇
前言 本篇文章的内容是对上一篇.net的retrofit--WebApiClient库的深层次补充,你可能需要先阅读上一篇才能理解此篇文章.本文将详细地讲解WebApiClient的原理,结合实际项目 ...
- 迭代器中next()的用法
>>> g = (x ** 2 for x in range(10)) >>> next(g) 0 >>> next(g) 1 >>& ...
- 为什么arguments是类数组对象
为什么JavaScript里函数的arguments只是array-like object? 只是标准这么规定的,还是有什么设计原因在里面?JavaScript的函数里面的arguments对象有 . ...
- 【eclipse】Target runtime Apache Tomcat v7.0 is not defined解决
在eclipse中导入项目时提示Target runtime Apache Tomcat v7.0 is not defined, 解决方法:右键项目--properties--targeted ru ...
- Spring-Blog:个人博客(一)-Mybatis 读写分离
概述: 2018,在平(tou)静(lan)了一段时间后,开始找点事情来做.这一次准备开发一个个人博客,在开发过程之中完善一下自己的技术.本系列博客只会提出一些比较有价值的技术思路,不会像写流水账一样 ...