故障排除delete[](损坏的未排序块)

Troubleshooting delete[] (corrupted unsorted chunks)

本文关键字:排序 损坏 排除 delete 故障      更新时间:2023-10-16

我的类IOBuffer管理一个内存缓冲区。它有一个grow()方法来增加底层缓冲区。

template<class T>
class IOBuffer
{
public:
    typedef T value_type;
    typedef T * pointer_type;
    typedef long size_type;
    IOBuffer(size_type size = 2048, const void * data = nullptr) 
    : data_(), eod_(), begin_(), end_()
    {
        grow(size);
        if (data)
        {
            memcpy(data_, data, size);
            push(size);
        }
    }
    IOBuffer(const IOBuffer & rhs)
    {
        size_type capacity = rhs.capacity();
        size_type size = rhs.size();
        data_ = new value_type[capacity];
        eod_ = data_ + capacity;
        begin_ = data_;
        end_ = data_ + size;
        memcpy(data_, rhs.begin_, size);
    }
    IOBuffer(IOBuffer && rhs)
    : data_(rhs.data_), eod_(rhs.eod_),
    begin_(rhs.begin_), end_(rhs.end_)
    {
        rhs.data_ = nullptr;
    }
    IOBuffer & operator =(const IOBuffer & rhs)
    {
        const size_type & sz = rhs.size();
        begin_ = data_;
        grow(sz);
        memcpy(begin_, rhs.begin_, sz);
        end_ = begin_ + sz;
        return *this;
    }
    IOBuffer & operator =(IOBuffer &&rhs)
    {
        std::swap(data_, rhs.data_);
        std::swap(eod_, rhs.eod_);
        std::swap(begin_, rhs.begin_);
        std::swap(end_, rhs.end_);
        return *this;
    }
    ~IOBuffer()
    {
        delete[] data_;
    }
    pointer_type data() const
    {
        return begin_;
    }
    template<typename U>
    const U * data_as() const
    {
        return reinterpret_cast<U*>(begin_);
    }
    size_type size() const
    {
        return end_ - begin_;
    }
    size_type capacity() const
    {
        return eod_ - data_;
    }
    pointer_type end() const
    {
        return end_;
    }
    size_type available() const
    {
        return eod_ - end_;
    }
    bool empty() const
    {
        return end_ == begin_;
    }
    bool grow(size_type newSize)
    {
        if (capacity() < newSize)
        {
            const size_type end = size();
            pointer_type temp = new value_type[newSize];
            if (data_ && end)
            {
                memcpy(temp, begin_, end * sizeof(value_type));
            }
            delete[] data_;
            data_ = temp;
            eod_ = data_ + newSize;
            begin_ = data_;
            end_ = data_ + end;
            return true;
        }
        else if(data_ != begin_)
        {
            const size_type end = size();
            memmove(data_, begin_, end * sizeof(value_type));
            begin_ = data_;
            end_ = begin_ + end;
        }
        return false;
    }
    size_type push(size_type n)
    {
        if (n > 0)
            end_ += n;
        assert(end_ >= data_ && end_ <= eod_);
        return n;
    }
    size_type consume(size_type n)
    {
        if (n > 0)
        {
            n = std::min(n, size());
            begin_ += n;
            assert(begin_ >= data_ && begin_ <= eod_);
        }
        return n;
    }
    void clear()
    {
        begin_ = end_ = data_;
    }
private:
    pointer_type data_;
    pointer_type eod_;
    pointer_type begin_;
    pointer_type end_;
};

奇怪的是,这种情况每天都会发生一次:

 *** glibc detected *** ./mdrelay: free(): corrupted unsorted chunks: 0x0000000001c48db0 ***

给我留下了一个核心垃圾场。调试转储(使用调试符号,我得到这个跟踪)

#0  0x0000003510832925 in raise (sig=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
#1  0x0000003510834105 in abort () at abort.c:92
#2  0x0000003510870837 in __libc_message (do_abort=2, 
fmt=0x3510958ac0 "*** glibc detected *** %s: %s: 0x%s ***n")
at ../sysdeps/unix/sysv/linux/libc_fatal.c:198
#3  0x0000003510876166 in malloc_printerr (action=3, 
str=0x3510958e48 "free(): corrupted unsorted chunks", ptr=<value optimized out>)
at malloc.c:6336
#4  0x0000003510878ca3 in _int_free (av=0x3510b8fe80, p=0x1c48da0, have_lock=0) at malloc.c:4832
#5  0x000000000042c529 in IOBuffer<unsigned char>::grow (this=0x1c48d18, newSize=4096)
at IOBuffer.hpp:125

和125行是delete[] data_内法生长。但是,当我打印出data_指向的内容时:

(gdb) p data_
$1 = (IOBuffer<unsigned char>::pointer_type) 0x1c495c0

是正确的

我对如何调用delete[]指向0x1c495c0_int_free()接收0x1c48da0感到困惑。这些指针之间的差值是2080,原始指针和崩溃地址之间的差值是2064。此外,(无法找到CentOS的glibc 2.12-1.132.el6_5.1和libstdc++ 4.7.3中使用的操作符new[]/delete[]的详细信息,而不是股票),但我发现2064出现在内存中,就在data_指向的地址之前。这将排除指针损坏。

(gdb) x/2dg data_ - 0x10
0x1c495b0:      2064    2064
(gdb) print (long)data_ - 0x1c48db0
$4 = 2064
(gdb) print (long)data_ - 0x1c48da0
$5 = 2080

可能操作符delete()在编译期间得到了优化(没有堆栈帧),我无法跟踪delete[]和_int_free()之间的指针发生了什么。对这件事有什么想法吗?

编辑:添加一些上下文,这个类,只是我认为管理像recv()或read()这样的函数的缓冲区而不需要复制的一种方式。它有四个指针:data_, eod_(缓冲区空间的开始和结束),begin_和end_(传入数据的开始和结束)。当一个函数read()返回n个字节时,end_被提前了这个n。因此,size()是end_ - begin_,即上次read()的大小。当缓冲区被处理时,begin_向前移动,直到遇到end_。如果由于end_和eod_之间没有足够的空间(或begin_遇到end_),缓冲区中还剩下一些内容,则调用grow()为下一次read()释放空间。当达到限制时,它可以增加整个缓冲区的大小,或者只是将剩余的数据移动到缓冲区空间的开始。

从这个场景来看,我认为你可能在其他地方写的数据超过了data_buffer的范围,你可能需要检查这个函数的代码。

另一方面,当调用memcpy/memmove时,你确定结束变量单位是字节,你是否需要从端到端改变它*sizeof(value_type)?

帖子中的课程版本似乎是稳定的,感谢周到的评论。

先前版本的类报告,在available()中,大于实际分配空间的缓冲区和recv()会导致缓冲区溢出。