正确托管堆的缓冲区损坏

问题描述 投票:1回答:1

我有一个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. 尝试使用新的池分配器或竞技场分配器来查看我们的自定义错误是否存在未知错误。

我正在寻找我没有考虑过的新方法。如何自动化这种方法,为这类问题提供更好的工具。如何验证对象背后没有变化?

c memory-management heap-memory
1个回答
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);
}
© www.soinside.com 2019 - 2024. All rights reserved.