zoukankan      html  css  js  c++  java
  • 一次__libc_message的排查

     信号是6,abort调用的。总体而言,当你malloc的指针为A,但是你free的指针不是A,则容易出这个错,当然假设你free的刚好是别人malloc的,则还是正常。
    还有一种是你free的地址在glibc里面记录的size有问题,也会报这个错,本文就是第二个情况。

     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

     映射到内存示意图上如下图所示:

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+  <--真正的chunk首指针
    |  prev_size, 前一个chunk的大小               | |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |  size, 低位作标志位,高位存放chunk的大小    |M|P|
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+  <--malloc成功返回的首指针
    |  正常时存放用户数据;                          .--------------我们的用户数据存放在此,这个例子中,相当于我们的数据有32个字节被踩了。
    .  空闲时存放malloc_chunk结构后续成员变量。       .
    .                                             .
    .                                             |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+  <--下一个chunk的首指针
    |             prev_size ……                    |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    可以看到,我们每次malloc返回的指针并不是内存块的首指针,前面还有两个size_t大小的参数,对于非空闲内存而言size参数最为重要。size参数存放着整个chunk的大小,由于物理内存的分配是要做字节对齐的,所以size参数的低位用不上,便作为flag使用。

      内存写溢出,通常就是把后一个chunk的size参数写坏了。
      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 
    ", $size
            if $argc == 1
                printf "List "
                whatis $arg0
                printf "Use plist <variable_name> <element_type> to see the elements in the list.
    "
            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 
    ", $size
            if $argc == 1
                printf "List "
                whatis $arg0
                printf "Use plist_member <variable_name> <element_type> <member> to see the elements in the list.
    "
            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 个元素,少掉的那个元素去哪了?没有进入链表唯一的可能是,链表中

    
    
    水平有限,如果有错误,请帮忙提醒我。如果您觉得本文对您有帮助,可以点击下面的 推荐 支持一下我。版权所有,需要转发请带上本文源地址,博客一直在更新,欢迎 关注 。
  • 相关阅读:
    CSS3 target伪类简介
    不用position,让div垂直居中
    css3 在线编辑工具 连兼容都写好了
    a标签伪类的顺序
    oncopy和onpaste
    【leetcode】1523. Count Odd Numbers in an Interval Range
    【leetcode】1518. Water Bottles
    【leetcode】1514. Path with Maximum Probability
    【leetcode】1513. Number of Substrings With Only 1s
    【leetcode】1512. Number of Good Pairs
  • 原文地址:https://www.cnblogs.com/10087622blog/p/7592320.html
Copyright © 2011-2022 走看看