отслеживание неинициализированных статических переменных - PullRequest
7 голосов
/ 19 октября 2011

Мне нужно отладить некрасивую и огромную математическую C-библиотеку, вероятно, когда-то созданную f2c. Код злоупотребляет локальными статическими переменными, и, к сожалению, где-то, похоже, используется тот факт, что они автоматически инициализируются равными 0. Если его функция входа вызывается с одним и тем же вводом дважды, это дает разные результаты. Если выгрузить библиотеку и перезагрузить ее снова, она работает правильно. Это должно быть быстро, поэтому я хотел бы избавиться от загрузки / выгрузки.

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

Я ищу места, где объявлена ​​локальная статическая переменная, сначала прочитано, а записано только позже. Проблема еще более усложняется тем фактом, что статические переменные иногда передаются через указатели (да - это ужасно).

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

Ответы [ 5 ]

5 голосов
/ 20 октября 2011

Дьявол кроется в деталях, но это может сработать для вас:

Сначала получите Frama-C . Если вы используете Unix, ваш дистрибутив может иметь пакет. Пакет не будет последней версией, но может быть достаточно хорошим и сэкономит вам время, если вы установите его таким образом.

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

int add(int x, int y)
{
  static int state;
  int result = x + y + state; // I tested it once and it worked.
  state++;
  return result;
}

Введите команду, например:

frama-c -lib-entry -main add -deps ugly.c

Опции -lib-entry -main add означают «посмотрите на функцию add». Опция -deps вычисляет функциональные зависимости. Вы найдете эти «функциональные зависимости» в журнале:

[from] Function add:
     state FROM state; (and default:false)
     \result FROM x; y; state; (and default:false)

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

Журнал показывает state как зависимость \result. Если вы ожидали, что возвращаемый результат будет зависеть только от аргументов (то есть два вызова с одинаковыми аргументами приводят к одному и тому же результату), это подсказка, что здесь может быть что-то не так с переменной state.

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

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

РЕДАКТИРОВАТЬ: вот более сложный пример:

void initialize_1(int *p)
{
  *p = 0;
}

void initialize_2(int *p)
{
  *p; // I made a mistake here.
}

int add(int x, int y)
{
  static int state1;
  static int state2;

  initialize_1(&state1);
  initialize_2(&state2);

  // This is safe because I have initialized state1 and state2:
  int result = x + y + state1 + state2; 

  state1++;
  state2++;
  return result;
}

В этом примере та же команда выдает результаты:

[from] Function initialize_1:
         state1 FROM p
[from] Function initialize_2:
[from] Function add:
         state1 FROM \nothing
         state2 FROM state2
         \result FROM x; y; state2

То, что вы видите для initialize_2 - это пустой список зависимостей, означающий, что функция ничего не назначает. Я поясню этот случай, отображая явное сообщение, а не просто пустой список. Если вы знаете, что должна делать любая из функций initialize_1, initialize_2 или add, вы можете сравнить это априорное знание с результатами анализа и увидеть, что что-то не так для initialize_2 и add.

ВТОРОЕ РЕДАКТИРОВАНИЕ: и теперь мой пример показывает что-то странное для initialize_1, поэтому, возможно, я должен объяснить это. Переменная state1 зависит от p в том смысле, что p используется для записи в state1, а если бы p было другим, то окончательное значение state1 было бы другим. Вот последний пример:

int t[10];

void initialize_index(int i)
{
  t[i] = 1;
}

int main(int argc, char **argv)
{
  initialize_index(argv[1][0]-'0');
}

С помощью команды frama-c -deps t.c зависимости, вычисленные для initialize_index:

[from] Function initialize_index:
         t[0..9] FROM i (and SELF)

Это означает, что каждая из ячеек зависит от i (его можно изменить, если i является индексом этой конкретной ячейки). Каждая ячейка также может сохранять свое значение (если i обозначает другую ячейку): это указывается с пометкой (and SELF) в последней версии и помечается более неясным (and default:true) в предыдущих версиях.

2 голосов
/ 20 октября 2011

То, что я сделал в конце, удаляет все статические классификаторы из кода с помощью «#define static». Это превращает неинициализированное статическое использование в недопустимое использование, и инструменты, на которые я охотюсь, могут быть обнаружены инструментами.

В моем конкретном случае этого было достаточно, чтобы определить место ошибки, но в более общей ситуации ее следует уточнить, если статические действительно делают что-то важное, путем постепенного повторного добавления «статического», когда код не может продолжить .

2 голосов
/ 20 октября 2011

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

К сожалению, я не могу рекомендовать ни один из инструментов в списке. Я знаком только с двумя коммерческими продуктами: Coverity и Klocwork . Укрытие очень хорошее (и дорогое). Klocwork это так (но дешевле).

1 голос
/ 19 октября 2011

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

rgrep "static \ s * int" path / to / src / root |grep -v = |grep -v "("

Это должно вернуть все статические переменные int, объявленные без знака равенства, а последний канал должен удалить что-либо с круглыми скобками в них (избавляясь от функций). Есть хорошее изменение, которое выигралне работает именно для вас, но игра с grep может быть самым быстрым способом отследить это.

Конечно, как только вы найдете тот, который работает, вы можете заменить int на все другие видыпеременные, чтобы искать те тоже. HTH

0 голосов
/ 20 октября 2011

Мой вопрос заключается в том, как раскрыть эти ошибки ...

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

Поэтому запрос инструмента, который автоматически найдет non -error, вряд ли даст удовлетворительный результат.

Из вашего описания видно, что somefunc() возвращает правильный результат при первом вызове и неверный результат при последующих вызовах.

Самый простой способ отладить такие проблемы - это иметь два параллельных сеанса GDB: один только что загруженный (будет вычислять правильный ответ) и один со «второй итерацией» (будет вычислять неправильный ответ). Затем выполните оба сеанса «параллельно» и посмотрите, где их вычисления или поток управления начинают расходиться.

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

...