• 一次__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 个元素,少掉的那个元素去哪了?没有进入链表唯一的可能是,链表中

    
    
    水平有限,如果有错误,请帮忙提醒我。如果您觉得本文对您有帮助,可以点击下面的 推荐 支持一下我。版权所有,需要转发请带上本文源地址,博客一直在更新,欢迎 关注 。
  • 相关阅读:
    WPF 快捷键读写txt
    win10 UWP GET Post
    win10 UWP GET Post
    win10 UWP Hmac
    win10 UWP Hmac
    win10 UWP MessageDialog 和 ContentDialog
    MySQL 触发器-更新字段时,status列会加一
    [SDOI2018]旧试题
    win10 UWP MessageDialog 和 ContentDialog
    win10 UWP RSS阅读器
  • 原文地址:https://www.cnblogs.com/10087622blog/p/7592320.html
Copyright © 2020-2023  润新知