具有正确管理的堆的缓冲区损坏

时间:2019-03-07 17:25:15

标签: c memory-management heap-memory

我有一个heisenbug发生得很少,以至于它不能在任何环境下复制,它会严重失败,而且我也不知道如何诊断它。

该错误与内存使用情况有关。 损坏不符合已定义的four categories损坏。

工具显示它不是:

  1. 未初始化的内存(先前已分配)
  2. 使用非拥有的内存(由线程分配并由线程拥有)
  3. 缓冲区溢出(通过边界检查)
  4. 错误的堆内存管理(这不是泄漏,所有内存都已释放,并且可以防御地将其设置为null)

我很自信地说这句话,因为虽然我不能复制它,但是在较高事务环境中的日志记录和工具表明上述情况不会发生。

我正在编译gcc c11,没有优化,Wall和其他最小标志。

ASAN,电围栏,hellgrind,memcheck和cppcheck都没有问题。

堆管理似乎可以与池分配器,边界检查和损坏标记一起使用。

绝对没有单元测试

主要在以下情况中出现此问题:非常很少的阵列损坏,设置了无效边界,只有50个项目,但是项目数被损坏,我们最终得到<0或>50。核心转储显示了这一点。通过确定此数组绑定来自何处并验证正确的值,我们可以避免此问题,但随后该问题将迁移到另一个位置。由于这仅影响单个客户和单个交易类型,因此向我指示与此客户或交易相关的内容。但是那棵树没有结果。

由于这种情况很少发生,因此我不能排除:

  1. 另一个线程损坏
  2. 某些数据竞争条件
  3. 线程竞争条件
  4. 不应编写编程错误来放置它。

我无法在模拟导致这种情况的环境中运行上述任何工具(ASAN,电围栏...)。但是我无法在可以运行这些工具的任何环境中复制它。

我唯一的想法是:

  1. 创建深层副本或序列化这些对象,并在整个代码库中进行拼接检查。 (混乱,由于内存限制,可能是不可能的)
  2. 忽略该问题(影响一个客户的不均,意味着我无法做到这一点)
  3. 通过所有这些错误检查以查找损坏,继续打乒乓球,并使代码库更加丑陋。
  4. 将所有内容全部用$ Language重写(不是一种选择)
  5. 尝试使用新的池分配器或竞技场分配器,以查看自定义漏洞中是否存在未知错误。

我正在寻找尚未考虑的新颖方法。自动执行此操作的方法,这些类型的问题的更好的工具。您如何验证对象在背后没有变化?

1 个答案:

答案 0 :(得分:1)

尽管这个问题很可能会以离题的形式结束,但是您将来可以用来帮助追查根本原因的通用工具是实现内存中的环形缓冲区来记录关键事件。它与常规日志不同,因为日志仅存储到内存中,因此延迟非常短。如果您有足够的内存专用于此日志,那么您应该能够在下一次崩溃时为客户检查它,并更好地了解导致损坏的事件。

一个非常基本的实现是:

static_assert(0 == (LR_TAPE_SIZE & (LR_TAPE_SIZE-1)),
        "LR_TAPE_SIZE must be a power of 2");
static_assert(LR_TAPE_SIZE > (LR_LOG_MAX + 1),
        "LR_TAPE_SIZE must be larger than LR_LOG_MAX");

struct lr_tape {
    uint32_t wrap :  1;
    uint32_t head : 31;
    char tape[LR_TAPE_SIZE];
};

int
lr_write(struct lr_tape *lr, const void *buf, uint32_t sz)
{
    uint32_t pos = lr->head % LR_TAPE_SIZE;
    uint32_t cnt = LR_TAPE_SIZE - pos;
    memcpy(&lr->tape[pos], buf, (cnt < sz) ? cnt : sz);
    if (cnt < sz) memcpy(&lr->tape[0], buf + cnt, sz - cnt);
    lr->head += sz;
    lr->wrap = lr->wrap || (lr->head >= LR_TAPE_SIZE);
    return sz;
}

然后,您可以为其实现一个简单的类似printf的包装器。

int
lr_log(struct lr_tape *lr, const char *fmt, ...)
{
    char buf[LR_LOG_MAX + 1];
    va_list ap;
    int r, p;
    va_start(ap, fmt);
    r = vsnprintf(buf, LR_LOG_MAX, fmt, ap);
    va_end(ap);
    if (r <= 0) return r;
    if (r >= LR_LOG_MAX) {
        r = LR_LOG_MAX;
        buf[r-3] = buf[r-2] = buf[r-1] = '.';
    }
    if (buf[r-1] != '\n') buf[r++] = '\n';
    return lr_write(lr, buf, r);
}

以及发出它的方法:

void
lr_output(struct lr_tape *lr, FILE *out)
{
    uint32_t pos = lr->head % LR_TAPE_SIZE;
    uint32_t cnt = LR_TAPE_SIZE - pos;
    if (lr->head == 0) return;
    if (lr->wrap) {
        fwrite("...", 3, 1, out);
        fwrite(&lr->tape[pos], cnt, 1, out);
    }
    fwrite(lr->tape, pos, 1, out);
}