Повреждение буфера с правильно управляемой кучей - PullRequest
1 голос
/ 07 марта 2019

У меня есть heisenbug, который встречается так редко, что его невозможно воспроизвести ни в одной среде, он не работает впечатляюще, и я не знаю, как его диагностировать.

Ошибка связана с использованием памяти. Коррупция не вписывается в определенные четыре категории коррупции.

Инструмент показывает, что это не так:

  1. Неинициализированная память (она была ранее выделена)
  2. Использование не принадлежащей памяти (она была выделена потоком и принадлежала потоку)
  3. Переполнение буфера (проход пограничных проверок)
  4. Неисправное управление памятью кучи (это не утечка, вся память освобождается и защищается нулем)

Я говорю это с некоторой уверенностью, потому что, хотя я не могу воспроизвести это, ведение журнала и инструментарий в средах с более высокими транзакциями показывают, что вышеописанное не происходит.

Я компилирую gcc c11 без оптимизации, Wall, другие минимальные флаги.

ASAN, электрическая изгородь, адская шкура, memcheck, cppcheck не находят проблем.

Управление кучей, кажется, хорошо работает с распределителем пула, проверкой границ, стражей коррупции.

Абсолютно нет юнит-тестов

Проблема в первую очередь проявляется, когда очень редко поврежден массив, установлены недопустимые границы, есть только 50 элементов, но количество элементов искажается, и мы получаем <0 или> 50. Дампы ядра показывают это. Определив, откуда исходит эта привязка массива и проверив правильное значение, мы можем предотвратить эту проблему, но затем проблема переносится в другое место. Так как это влияет только на одного клиента и один тип транзакции, который указывает мне что-то, связанное с этим клиентом или транзакцией Но это дерево не принесло плодов.

Из-за того, как часто это происходит, я не могу исключить:

  1. Повреждение еще одного потока
  2. Некоторые условия гонки данных
  3. Состояние резьбы
  4. Программирование ошибок записи на место не должно.

Я не могу запустить любой из вышеперечисленных инструментов (ASAN, электрический забор, ...) в среде, которая имитирует условия, которые вызывают это. Но я не могу воспроизвести его в любой среде, в которой я могу запустить эти инструменты.

Мои единственные мысли были бы:

  1. Создавайте глубокие копии или сериализуйте эти объекты и объединяйте проверки по всей базе кода. (грязно, может быть невозможно из-за ограничений памяти)
  2. Игнорировать проблему (неравномерное воздействие на одного клиента означает, что я не могу этого сделать)
  3. Продолжайте играть в пинг-понг и сделайте кодовую базу еще более уродливой со всеми этими проверками ошибок в поисках повреждений.
  4. Переписать все это на $ Language (не совсем вариант)
  5. Попробуйте новый распределитель пула или распределителя арены, чтобы увидеть, есть ли неизвестная ошибка в нашей пользовательской.

Я ищу новые подходы к этому, которые я не рассматривал. Способы автоматизации этого, лучший инструмент для решения подобных проблем. Как вы подтверждаете, что объект не изменился за вашей спиной?

1 Ответ

1 голос
/ 07 марта 2019

Хотя этот вопрос, скорее всего, будет закрыт как не по теме, универсальный инструмент, который вы можете использовать, чтобы помочь отследить основную причину в будущем, заключается в реализации кольцевого буфера в памяти для записи критических событий. Он отличается от обычных журналов, поскольку журнал хранится только в памяти и поэтому имеет очень низкую задержку. Если у вас достаточно памяти, выделенной для этого журнала, вы сможете проверить его при следующем сбое для клиента и лучше понять события, которые привели к повреждению.

Очень простая реализация:

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);
}
...