LINUX.ORG.RU

Повреждение данных после передачи структуры по указателю

 , , , ,


0

2

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

В один прекрасный момент, в одной из функций, при передаче нескольких структур, данные во всех трех превращаются в тыкву.

void poly_mul(const Poly *p, const Poly *q, Poly *newp){
    newp->length = p->length + q->length - 1;
    memset(newp->ptr(), 0, newp->length * sizeof(uint8_t)); <-- Вот тут все ломается при попытке замемсетить левую область памяти.
    /* Compute the polynomial multiplication (just like the outer product of two vectors,
     * we multiply each coefficients of p with all coefficients of q) */
    for(uint8_t j = 0; j < q->length; j++){
        for(uint8_t i = 0; i < p->length; i++){
            newp->at(i+j) ^= mul(p->at(i), q->at(j)); /* == r[i + j] = gf_add(r[i+j], gf_mul(p[i], q[j])) */
        }
    }
}

void GeneratorPoly() {
    Poly *gen = polynoms + ID_GENERATOR;
    gen->at(0) = 1;
    gen->length = 1;

    Poly *mulp = polynoms + ID_TPOLY1;
    Poly *temp = polynoms + ID_TPOLY2;
    mulp->length = 2;

    for(int8_t i = 0; i < ecc_length; i++){
        mulp->at(0) = 1;
        mulp->at(1) = gf::pow(2, i);

        gf::poly_mul(gen, mulp, temp);
        *gen = *temp;
    }
}

int abstract_func() {
    /* Allocating memory on stack for polynomials storage */
    uint8_t stack_memory[MSG_CNT * msg_length + POLY_CNT * ecc_length * 2];
    
    // Каждый класс Poly хранит указатель на указатель memory и разыменовывает его для доступа к памяти на стеке.
    // Сам массив классов тоже расположен на стеке.
    this->memory = stack_memory;

    GeneratorPoly();
}

Алгоритм написанного описывать не буду, это в принципе не так уж важно, но если кому интересно, это кодирование Рида-Соломона.

Вывод дебаггера перед вызовом poly_mul:
gen: 0x41a7c2
gen->length: 1
gen->size: 16
gen->offset: 116
gen->memory: 0x41a7a0
*gen->memory: 0x7fffc1f67550
Для 2х других аналогично

Вывод дебаггера внутри poly_mul
gen: 0x41a7c2
gen->length: 1
gen->size: 2
gen->offset: 4
gen->memory: 0x403020000000000
*gen->memory: ------

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

Возможно это важно: отдельно модуль кодирования Рида-Соломона проходит все тесты в любых режимах, ошибки начали сыпаться при использовании кодирования внутри другого проекта.
Собственно, в дебаг режиме все пашет, а в релизе — куй.

★★★★★

Последнее исправление: mersinvald (всего исправлений: 2)

*gen = *temp; заменит все и вся, нет ничего подозрительного ?

anonymous
()

телекинез сегодня сбоит из-за низкой облачности, поэтому сложно гадать, что там хранится внутри Poly. но подсчёт размеров для обнуления какой-то явно подозрительный. нет проверок на входящие параметры и вообще код похож на тыкву, даже возвращает указатель на локальный объект, как выше заметили.

Iron_Bug ★★★★★
()
Ответ на: комментарий от ProstoTyoma

Нет, за пределами функции она и не нужна.
В функциях, которые вызваны внутри abstract_func и глубже она ведь будет доступна.

mersinvald ★★★★★
() автор топика
Ответ на: комментарий от Iron_Bug

Так выход не происходит, а он все равно превращается тыкву

mersinvald ★★★★★
() автор топика
Ответ на: комментарий от mersinvald

Это понятно. А до этого что? Там точно других ошибок нет? Если на дебаговом билде молчит то проверяй на релизе.

true_admin ★★★★★
()

во первых массив обьектов, классы это лишь шаблоны по которым создаются обьекты. длина и размер это тафтология в данном случае. во вторых как инициализируется массив polynoms ? в третьих как создаются и назначаются указатели на stack_memory (не видна связь между this->memory и Poly)? в четвертых ID_TPOLY2 точно не превышает значения элементов в массиве polynoms ? в пятых нужен минимальный пример который можно скомпилить и в котором остается баг

anonymous
()
Ответ на: комментарий от anonymous

в пятых нужен минимальный пример который можно скомпилить и в котором остается баг

мне кажется, с этого и стоило начинать

kiotoze ★★★★
()
Ответ на: комментарий от anonymous

в четвертых ID_TPOLY2

Точно

в пятых нужен минимальный пример который можно скомпилить и в котором остается баг

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

mersinvald ★★★★★
() автор топика
Ответ на: комментарий от true_admin

Что-то про неинициализированные переменные, но это бред — все инициализировано.
Думаю, что это из-за аллокации на стеке, valgring больше с динамической памятью работает.

mersinvald ★★★★★
() автор топика
Ответ на: комментарий от mersinvald

Почему так много людей отмахиваются от valgrind? Я ещё ни разу не видел у него ложных срабатываний. У ASAN видел один раз.

i-rinat ★★★★★
()
Ответ на: комментарий от true_admin

Окей, тогда попробую все ошибки в дебаг-моде исправить, если не исчезнет, отпишусь.

mersinvald ★★★★★
() автор топика
Ответ на: комментарий от true_admin

Поправил все ошибки, связанные с неинициализированными переменными, valgrind теперь молчит на дебаге, на релизе все тот же единственный

Invalid read of size 8
  in ptr in /home/mike/dev/c++/Skaut/FATFS/Reed-Solomon/include/poly.hpp:108
Address 0x8205000000000000 is not stack'd, malloc'd or (recently) free'd  1: ptr in /home/mike/dev/c++/Skaut/FATFS/Reed-Solomon/include/poly.hpp:108
  2: RS::gf::poly_mul(RS::Poly const*, RS::Poly const*, RS::Poly*) in ../FATFS/Reed-Solomon/src/gf.cpp:43
  3: RS::ReedSolomon<(unsigned char)128, (unsigned char)2>::GeneratorPoly() in /home/mike/dev/c++/Skaut/FATFS/Reed-Solomon/include/rs.hpp:287
  4: RS::ReedSolomon<(unsigned char)128, (unsigned char)2>::EncodeBlock(void const*, void*) in /home/mike/dev/c++/Skaut/FATFS/Reed-Solomon/include/rs.hpp:93
  5: Memory::HAL::WritePage(unsigned int, void const*) in /home/mike/dev/c++/Skaut/FATFS/NAND/HAL/hardware_adaption_layer.cpp:43
  6: Memory::FTL::WriteSector(unsigned int, void const*) in /home/mike/dev/c++/Skaut/FATFS/NAND/FTL/flash_translation_layer.cpp:28
  7: disk_write in /home/mike/dev/c++/Skaut/FATFS/diskio.cpp:83
  8: f_mkfs in /home/mike/dev/c++/Skaut/FATFS/ff.c:4246
  9: main in /home/mike/dev/c++/Skaut/FATFS/main.cpp:35

mersinvald ★★★★★
() автор топика
Ответ на: комментарий от mersinvald

Подозреваю, кто-то портит память таким образом что vlgrind этого не видит. У меня такое было когда я затирал часть стэка при выходе за пределы массива аллоцированного на стэке.

Что я правильно понимаю что у тебя до вызова твоей функции абсолютно всё в порядке? Тогда попробуй сделать make clean, etc. Я видел такие проблемы когда заголовочные файлы не соответствовали реальному API/ABI. Так же, возможно, какие-то флаги сборки? К сожалению, в крестах я не силён, возможно, там свои грабли о которых я не знаю. Надеюсь, ты собираешь через clang с -Wall? А то оно часто находит неожиданные проблемы, gcc не катит.

Ну и у тебя не применяются треды?

true_admin ★★★★★
()
Ответ на: комментарий от true_admin

100 раз пересобирал уже, разными компиляторами, с разными опциями, включая полное вырезание всех расширений до стандрарта c++99.
Собираю шлангом, да, флаги: -std=c++98 -pedantic -Wall -Wno-old-style-cast -O0
Никаких тредов, все однопоточное...

mersinvald ★★★★★
() автор топика
Ответ на: комментарий от true_admin

Память перезатирается как-то слишком избирательно, именно при вызове функции.
Провал стека исключаем, это было бы возможно на stm32, под который пишу, но дебажу я на линуксах c i5
Я уже вообще не представляю что это может быть и почему так Оо

mersinvald ★★★★★
() автор топика
Ответ на: комментарий от mersinvald

Выкладывай исходники :).

На какой итерации poly_mul оно выскакивает? Вообще, моя теория, проблема в newp->ptr(). Попробуй вызвать этот метод отдельно, уверен, valgrind ругнётся ибо сам по себе memset может только invalid write сделать, но не read.

true_admin ★★★★★
()
Ответ на: комментарий от true_admin

На первой :)

Исходники довольно объемистые, лучше, наверное, скажи что конкретно показать.

Попробуй вызвать этот метод отдельно

Этот метод отдельно кучу раз вызывается, все нормально и только в этой вложенной функции все падает при попытке разыменовать указатель, содержащий левый адрес.



UPD: Уже интереснее, уронил и Debug — -O2 приводит к тому же результату.

mersinvald ★★★★★
() автор топика
Ответ на: комментарий от true_admin

Что-то идет не так при генерации кода с -O2, конкретно при вызове сторонних функций.
Если я передаю указатель в метод, все нормально, если передаю в обычную статическую функцию — падает, хотя метод — всего лишь функция с неявной передачей this.
И то и то использует только открытые методы и поля класса Poly, никаких обращений к приватным данным (да и компилятор бы не дал этого сделать).
Также пытался вызывать напрямую, без GeneratorPoly, чтобы избежать лишнего уровня вложенности — то же самое.

mersinvald ★★★★★
() автор топика
Ответ на: комментарий от true_admin

Класс Poly:

struct Poly {
    Poly()
        : length(0), _memory(nullptr) {}

    Poly(uint8_t id, uint16_t offset, uint8_t size) \
        : length(0), _id(id), _size(size), _offset(offset), _memory(nullptr) {}

    /* @brief Append number at the end of polynomial
     * @param num - number to append
     * @return false if polynomial can't be stretched */
    inline bool Append(uint8_t num) {
        #ifdef DEBUG
        assert(length+1 < _size);
        #endif
        ptr()[length++] = num;
        return true;
    }

    /* @brief Polynomial initialization */
    inline void Init(uint8_t id, uint16_t offset, uint8_t size, uint8_t** memory_ptr) {
        this->_id     = id;
        this->_offset = offset;
        this->_size   = size;
        this->length  = 0;
        this->_memory = memory_ptr;
    }

    /* @brief Polynomial memory zeroing */
    inline void Reset() {
        memset((void*)ptr(), 0, this->_size);
    }

    /* @brief Copy polynomial to memory
     * @param src    - source byte-sequence
     * @param size   - size of polynomial
     * @param offset - write offset */
    inline void Set(const uint8_t* src, uint8_t len, uint8_t offset = 0) {
        #ifdef DEBUG
        assert(src && len <= this->_size-offset);
        #endif
        memcpy(ptr()+offset, src, len * sizeof(uint8_t));
        length = len + offset;
    }

    #define max(a, b) ((a > b) ? (a) : (b))

    inline bool operator== (Poly &b) const {
        return memcmp(ptr(), b.ptr(), max(this->length, b.length)) == 0;
    }

    inline bool operator!= (Poly &b) const {
        return memcmp(ptr(), b.ptr(), max(this->length, b.length));
    }

    inline void operator=  (const Poly &b) {
        length = max(length, b.length);
        Set(b.ptr(), length);
    }

    inline uint8_t& operator[] (uint8_t i) const {
        #ifdef DEBUG
        assert(i < _size);
        #endif
        return ptr()[i];
    }

    inline uint8_t& at(uint8_t i) const {
        #ifdef DEBUG
        assert(i < _size);
        #endif
        return ptr()[i];
    }

    inline uint8_t id() const {
        return _id;
    }

    inline uint8_t size() const {
        return _size;
    }

    // Returns pointer to memory of this polynomial
    inline uint8_t* ptr() const {
        #ifdef DEBUG
        assert(_memory && *_memory);
        #endif
        return (*_memory) + _offset;
    }

    uint8_t length;

protected:

    uint8_t   _id;
    uint8_t   _size;    // Size of reserved memory for this polynomial
    uint16_t  _offset;  // Offset in memory
    uint8_t** _memory;  // Pointer to pointer to memory
};

Инициализируется массив этих объектов следующим образом:

const uint8_t   enc_len  = msg_length + ecc_length;
const uint8_t   poly_len = ecc_length * 2;
uint8_t** memptr   = &memory;
uint16_t  offset   = 0;

polynoms[0].Init(ID_MSG_IN, offset, enc_len, memptr);
offset += enc_len;

polynoms[1].Init(ID_MSG_OUT, offset, enc_len, memptr);
offset += enc_len;

for(uint8_t i = ID_GENERATOR; i < ID_MSG_E; i++) {
    polynoms[i].Init(i, offset, poly_len, memptr);
    offset += poly_len;
}

polynoms[5].Init(ID_MSG_E, offset, enc_len, memptr);
offset += enc_len;
for(uint8_t i = ID_TPOLY3; i < ID_ERR_EVAL+2; i++) {
    polynoms[i].Init(i, offset, poly_len, memptr);
    offset += poly_len;
}

P.S Буду признателен за критику любого кода, приведенного в треде

mersinvald ★★★★★
() автор топика
Последнее исправление: mersinvald (всего исправлений: 2)
Ответ на: комментарий от true_admin

Но я сомневаюсь что проблема именно здесь — отдельно этот код тесты проходит, в связке с кодом FATFS — нет.

mersinvald ★★★★★
() автор топика
Ответ на: комментарий от true_admin

Апдейт: сами объекты не повреждаются, массив целый, после выхода из сбойной функции данные в объектах абсолютно целые.
Как такое может быть, если внутри функции объект даже не копируется — просто разыменовывается указатель??? О_О

mersinvald ★★★★★
() автор топика
Ответ на: комментарий от true_admin

Nailed it!
Заинлайнил функции — все пришло в норму. И все же это не выход, но может хотя бы наводку даст, что могло произойти.

mersinvald ★★★★★
() автор топика
Ответ на: комментарий от mersinvald

Ты пробовал выносить вызов ptr() за пределы memset (и убрать inline)? У меня есть чувство что оно может заработать, но я пока затрудняюсь в деталях описать почему.

По поводу исходников. Это же проект, он уже где-то лежит на гитхабе? Просто дай ссылку и комманда как собрать, как вызвать ошибку. Потому что так можно долго в испорченый телефон играть. Я вот, так понял, до https://github.com/google/sanitizers ты не дошёл.

true_admin ★★★★★
()
Ответ на: комментарий от true_admin

Дошел, никаких ошибок
Проблема в том что я без понятия как воспроизводить.
Код самого кодирования тут: тык
Но само по себе оно работает, а в составе драйвера NAND — нет.

Сейчас спрошу у руководства можно ли драйвер выкинуть на гитхаб

mersinvald ★★★★★
() автор топика
Последнее исправление: mersinvald (всего исправлений: 3)
Ответ на: комментарий от true_admin

Ты пробовал выносить вызов ptr() за пределы memset (и убрать inline)?

Попробовал, все то же самое.

mersinvald ★★★★★
() автор топика
Последнее исправление: mersinvald (всего исправлений: 1)
Ответ на: комментарий от mersinvald

Мне не нравится *gen = *temp в контексте того что _memory останется общим. У меня не хватает терпения детально разобраться в работе кода, но подумай может ли это быть проблемой. Я такие ошибки несколько раз встречал у себя в коде.

Мне вообще сама концепция расшареной памяти между объектами в которую все пишут не нравится. Это лучший способ сделать так что потом концов не сыщешь.

true_admin ★★★★★
()
Ответ на: комментарий от true_admin

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

Мне не нравится *gen = *temp

Мне тоже, это легаси хотфикс, при переходе на указатели, раньше все было на ссылках, переделать однозначно надо, но проблема точно не тут.

mersinvald ★★★★★
() автор топика
Ответ на: комментарий от mersinvald

Больше идей у меня нет, кроме как по шагам выполнять и смотреть. Так же можно попробовать gdb reverse execution, расскажи если это работает (я ни разу не пользовался этим).

Кстати, можно не окружать assert в DEBUG. Используй NDEBUG, см. man assert.

true_admin ★★★★★
()
Ответ на: комментарий от mersinvald
max(this->length, b.length)

это очень опасное допущение. нет проверки, что size<=b.length и b.size<=length. в один прекрасный момент туда загонят полиномы с разной длиной и кердык.

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 1)
Ответ на: комментарий от Iron_Bug

Действительно, спасибо.
От memcmp ничего страшного, кроме возможной попаболи возжелавшего поправить мой код, не случится, но все таки не хорошо :)

mersinvald ★★★★★
() автор топика
Последнее исправление: mersinvald (всего исправлений: 1)
Ответ на: комментарий от true_admin

Я уже даже ассемблерный выхлоп курил — непонятно все равно откуда такое поведение.
Реверс у меня не работает, ну, или я не разобрался)

Кстати, можно не окружать assert в DEBUG. Используй NDEBUG, см. man assert.

Да, мне уже указывали на это в галерее.
Тут немного другая цель — не включать assert.h без дефайна DEBUG — в целевом армовском тулчейне он отсутствует.

mersinvald ★★★★★
() автор топика
Ответ на: комментарий от mersinvald

не включать assert.h без дефайна DEBUG

#ifdef DEBUG
#include <assert.h>
#else
#define assert(...)
#endif
i-rinat ★★★★★
()

Код просто адовый. Ihmo проще за часик переписать эту часть кода на чистом C, если это возможно в рамках данного проекта - по крайней мере его не нужно будет долго отлаживать.

anonymous
()
Ответ на: комментарий от mersinvald

Буду признателен за критику любого кода, приведенного в треде

Ты пишешь на смеси C и C++. Этого достаточно, чтобы назвать код говном. Не ленись и изучи STL/C++14

Solace ★★
()

На современном C++ так уже лет 5 не пишут. Использование memset даже в C считается давно плохим тоном. Перепишите код нормально.

anonymous
()
Ответ на: комментарий от anonymous

На современном C++ так уже лет 5 не пишут

Судя по -std=c++98, ТС не пишет на современном Си++.

И кстати, что используют вместо memset в «современном Си++»?

tailgunner ★★★★★
()
Ответ на: комментарий от Solace

в целевом армовском тулчейне
STL/C++14

/0

mersinvald ★★★★★
() автор топика
Ответ на: комментарий от mersinvald

Ну в C свои способы, начиная от bzero и = { 0 }, кончая всякими косвенными способами типа mmap. Вообще-то все это давно известно и десятки раз пережевывалось на различных площадках. На ЛОРе время похоже остановилось где-то в конце 90х...

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.