SlideShare una empresa de Scribd logo
1 de 10
Descargar para leer sin conexión
Статический анализ кода для
верификации 64-битных приложений
Авторы: Андрей Карпов, Евгений Рыжков

Дата: 22.04.2007


Аннотация
В результате появления на рынке персональных компьютеров 64-битных процессоров, перед
разработчиками программ возникает задача переноса старых 32-битных приложений на новую
платформу. После такого переноса кода приложение может вести себя некорректно. В статье
рассматривается вопрос разработки и применения статического анализатора кода для проверки
правильности таких приложений. Приводятся проблемы, возникающие в приложениях после
перекомпиляции для 64-битных систем, а также правила, по которым выполняется проверка кода.

Данная статья содержит различные примеры 64-битных ошибок. Однако с момента ее написания,
мы узнали значительно больше примеров и типов ошибок, которые не описаны в этой статье. Мы
предлагаем вам познакомиться со статьей "Коллекция примеров 64-битных ошибок в реальных
программах", в которой наиболее полно описаны известные нам дефекты в 64-битных
программах. Также рекомендуем изучить "Уроки разработки 64-битных приложений на языке
Си/Си++", где описана методика создания корректного 64-битного кода и методы поиска всех
видов дефектов с использованием анализатора кода Viva64.


1. Введение
Массовое производство и повсеместная доступность 64-битных процессоров привели
разработчиков приложений к необходимости разработки 64-битных версий своих программ. Ведь
для того, чтобы пользователи могли получить реальные преимущества от использования новых
процессоров, приложения должны быть перекомпилированы для поддержки 64-битной
архитектуры. Теоретически этот процесс не должен представлять проблем. Однако на практике
часто после перекомпиляции приложение работает не так, как должно. Это может проявляться
самым широким образом: от порчи файлов с данными, до отказа работы справочной системы.
Причина такого поведения кроется в изменении размеров базовых типов данных в 64-битных
процессорах, а точнее - в изменении соотношений между типами. Именно поэтому основные
проблемы при переносе кода обнаруживаются в приложениях, разработанных с использованием
низкоуровневых языков программирования типа C или C++. В языках с четко структурированной
системой типов (например, языки .NET Framework), как правило, таких проблем не возникает.

В чем же проблема именно с низкоуровневыми языками? Дело в том, что даже все
высокоуровневые конструкции и библиотеки C++ в конечном итоге реализованы с
использованием низкоуровневых типов данных, таких как указатель, машинное слово и т.п.
Поскольку при изменении архитектуры эти типы данных меняются, то и поведение программ
также может измениться.

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

Поэтому возникает задача поиска в исходном коде программы тех мест, которые при переносе с
32-битной на 64-битную архитектуру могут работать неправильно. Решению такой задачи и
посвящена настоящая статья.


2. Примеры проблем, возникающих при переносе кода на 64-битные
системы
Приведем несколько примеров, когда после переноса кода на 64-битную систему, в приложении
могут проявиться новые ошибки. Другие примеры можно найти в различных статьях [1, 2].

При расчете необходимой для массива памяти использовался явно размер типа элементов. На 64-
битной системе этот размер изменился, но код остался прежним:

size_t ArraySize = N * 4;

intptr_t *Array = (intptr_t *)malloc(ArraySize);

Некоторая функция возвращала значение -1 типа size_t в случае ошибки. Проверка результата
была записана так:

size_t result = func();

if (result == 0xffffffffu) {

// error

}

На 64-битной системе значение -1 для этого типа выглядит уже по-другому и проверка не
срабатывает.

Арифметика с указателями - постоянный источник проблем. Но в случае с 64-битными
приложениями к уже известным добавляются новые проблемы. Рассмотрим пример:

unsigned a16, b16, c16;

char *pointer;

...

pointer += a16 * b16 * c16;

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

Все эти и многие другие ошибки были обнаружены в реальных приложениях во время переноса
их на 64-битную платформу.
3. Обзор существующих решений
Существуют различные подходы к обеспечению корректности кода приложений. Перечислим
наиболее распространенные из них: тестирование с помощью юнит-тестов, динамический анализ
кода (во время работы приложения), статический анализ кода (анализ исходных текстов). Нельзя
сказать, что какой-то один вариант тестирования лучше других - все эти подходы обеспечивают
различные аспекты качества приложений.

Юнит-тесты предназначены для быстрой проверки небольших участков кода, например,
отдельных функций и классов [3]. Их особенность в том, что эти тесты выполняются быстро и
допускают частый запуск. Из этого вытекают два нюанса использования такой технологии. Во-
первых, эти тесты должны быть написаны. Во-вторых, тестирование выделения больших объемов
памяти (например, более двух гигабайт) занимает значительное время, поэтому
нецелесообразно, так как юнит-тесты должны отрабатываться быстро.

Динамические анализаторы кода (лучший представитель - это Compuware BoundsChecker)
предназначены для обнаружения ошибок в приложении во время выполнения программы. Из
этого принципа работы и вытекает основной недостаток динамического анализатора. Для того,
чтобы убедиться в корректности программы, необходимо выполнить все возможные ветки кода.
Для реальной программы это может быть затруднительно. Но это не значит, что динамический
анализ кода не нужен. Такой анализ позволяет обнаружить ошибки, которые зависят от действий
пользователя и не могут быть определены по коду приложения.

Статические анализаторы кода (как, например, Gimpel Software PC-lint и Parasoft C++test)
предназначены для комплексного обеспечения качества кода и содержат несколько сотен
анализируемых правил [4]. В них также есть некоторые из правил, анализирующих корректность
64-битных приложений. Однако, поскольку это анализаторы кода общего назначения, то их
использование для обеспечения качества 64-битных приложений не всегда удобно. Это
объясняется, прежде всего, тем, что они не предназначены именно для этой цели. Другим
серьезным недостатком является их ориентированность на модель данных, используемую в Unix-
системах (LP64). В то время как модель данных, используемая в Windows-системах (LLP64),
существенно отличается от нее. Поэтому применение этих статических анализаторов для проверки
64-битных Windows-приложений возможно только после неочевидной дополнительной
настройки.

Некоторым дополнительным уровнем проверки кода можно считать наличие в компиляторах
специальной диагностики потенциально некорректного кода (например, ключ /Wp64 в
компиляторе Microsoft Visual C++). Однако этот ключ позволяет отследить лишь наиболее
некорректные конструкции, в то время как многие из также опасных операций он пропускает.

Возникает вопрос: "Может быть, проверка кода приложений при переносе на 64-битные системы
не нужна, поскольку таких ошибок в приложении будет не так много?". Мы считаем, что такая
проверка необходима хотя бы потому, что крупнейшие компании (например, IBM и Hewlett-
Packard) разместили на своих сайтах статьи [2], посвященные возникающим при переносе кода
ошибкам.


4. Правила анализа корректности кода
Мы сформулировали 10 правил поиска опасных конструкций языка C++ с точки зрения переноса
кода на 64-битную систему. Перед описанием правил необходимо напомнить о понятии значащих
бит. Говоря о количестве значащих бит, мы учитываем, что отрицательные значения используют
все биты данного типа:

int a = 1; // Используется 1 бит. (0x00000001)

int b = -1; // Используется 32 бита. (0xFFFFFFFF)

В правилах используется специально введенный тип memsize. Под memsize-типом мы будем
понимать любой простой целочисленный тип, способный хранить в себе указатель и меняющий
свою размерность при изменении разрядности платформы с 32 бит на 64 бита. Примеры memsize-
типов: size_t, ptrdiff_t, все указатели, intptr_t, INT_PTR, DWORD_PTR.

Теперь перечислим сами правила и приведем примеры их применения.

ПРАВИЛО 1.
Следует считать опасными конструкции явного и неявного приведения целых типов размерностью
32 бита к memsize типам:

unsigned a;

size_t b = a;

array[a] = 1;

Исключения:

1) Приводимый 32-битный целый тип является результатом выражения, где для представления
значения выражения требуется меньше 32 бит:

unsigned short a;

unsigned char b;

size_t c = a * b;

При этом выражение не должно состоять только из числовых литералов:

size_t a = 100 * 100 * 100;

2) Приводимый 32-битный тип представлен числовым литералом:

size_t a = 1;

size_t b = 'G';

ПРАВИЛО 2.
Следует считать опасными конструкции явного и неявного приведения memsize-типов к целым
типам размерностью 32 бита:

size_t a;

unsigned b = a;

Исключение:

Приводимый тип size_t является результатом выполнения оператора sizeof():
int a = sizeof(float);

ПРАВИЛО 3.
Опасной следует считать виртуальную функцию, удовлетворяющую ряду условий:

а) функция объявлена в базовом классе и в классе-потомке.

б) типы аргументов функций не совпадают, но эквивалентны на 32-битной системе (например:
unsigned, size_t) и не эквивалентны на 64-битной.

class Base {

  virtual void foo(size_t);

};

class Derive : public Base {

  virtual void foo(unsigned);

};

ПРАВИЛО 4.
Опасными следует считать вызовы перегруженных функций с аргументом типа memsize. При этом
функции должны быть перегружены для целых 32-битных и 64-битных типов данных:

void WriteValue(__int32);

void WriteValue(__int64);

...

ptrdiff_t value;

WriteValue(value);

ПРАВИЛО 5.
Опасным следует считать явное приведение одного типа указателя к другому, если один из них
ссылается на 32-х/64-x битный тип, а другой на memsize-тип:

int *array;

size_t *sizetPtr = (size_t *)(array);

ПРАВИЛО 6.
Опасным следует считать явные и неявные приведения memsize-типа к double и наоборот:

size_t a;

double b = a;

ПРАВИЛО 7.
Опасным следует считать передачу memsize-типа в функцию с переменным количеством
аргументов:
size_t a;

printf("%u", a);

ПРАВИЛО 8.
Опасным следует считать использование ряда магических констант (4, 32, 0x7fffffff, 0x80000000,
0xffffffff):

size_t values[ARRAY_SIZE];

memset(values, ARRAY_SIZE * 4, 0);

ПРАВИЛО 9.
Опасным следует считать наличие в объединениях (union) членов memsize-типов:

union PtrNumUnion {

    char *m_p;

    unsigned m_n;

} u;

...

u.m_p = str;

u.m_n += delta;

ПРАВИЛО 10.
Опасными следует считать генерацию и обработку исключений с использованием memsize-типов:

char *p1, *p2;

try {

    throw (p1 - p2);

}

catch (int) {

    ...

}

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

Представленные правила реализованы в статическом анализаторе кода Viva64. Принцип его
работы рассматривается в следующем разделе.
5. Архитектура анализатора
Работа анализатора состоит из нескольких этапов, часть из которых свойственна обычным
компиляторам C++ (рисунок 1).




                            Рисунок 1. Архитектура анализатора.

На вход анализатора поступает файл с исходным кодом, а в результате его работы генерируется
отчет о потенциальных ошибках в коде с номерами строк. Этапы работы анализатора:
препроцессорная обработка, построение дерева кода и собственно анализ.

На этапе препроцессорной обработки выполняется подключение файлов, объявленных с
помощью #include-директив, а также обработка параметров условной компиляции (#ifdef/#endif).

В результате разбора (parsing) файла полученного после препроцессорной обработки, строится
дерево кода с той информацией, которая в дальнейшем необходима для анализа. Рассмотрим
простой пример:

int A, B;

ptrdiff_t C;

C = B * A;

В этом коде есть потенциальная проблема, связанная с различными типами данных. Так,
переменная C здесь никогда не сможет принять значение меньше или больше 2 Гигабайт, что
может быть неправильно. Анализатор должен сообщить, что в строке "C = B * A" потенциально
некорректная конструкция. Вариантов исправления этого кода несколько. Если переменные B и A
никогда не могут принимать по смыслу значения больше 2 гигабайт, но переменная C может, то
записать выражение следует так:

C =   (ptrdiff_t)(B) * (ptrdiff_t)(A);

Но если переменные A и B на 64-битной системе могут принимать большие значение, то надо
исправить их тип на ptrdiff_t:

ptrdiff_t A;

ptrdiff_t B;

ptrdiff_t C;
C = B * A;

Покажем, как это выполняется на уровне анализа дерева кода.

Сначала для кода строится дерево (рисунок 2).




                                    Рисунок 2. Дерево кода.

Затем на этапе анализа дерева необходимо определить типы переменных, участвующих в
вычислении выражения. Для этого используется вспомогательная информация, полученная во
время построения дерева (модуль хранения типов), как показано на рисунке 3.
Рисунок 3. Хранение информации о типах.

После определения типов всех переменных, участвующих в выражении, необходимо вычислить
результирующие типы подвыражений. В рассматриваемом примере необходимо определить тип
результата промежуточного выражения "B * A". Это делается с помощью модуля вычисления
типов как показано на рисунке 4.




                          Рисунок 4. Вычисление типа выражений.

Затем выполняется проверка при вычислении типа результирующего выражения (операция "=" в
нашем примере) и в случае конфликта типов конструкция помечается как потенциально опасная.
В рассматриваемом примере такой конфликт имеет место, так как переменная C имеет размер 64
бита (на 64-битной системе), а результат выражения "B * A" - 32 бита.

Аналогичным образом выполняется анализ других правил, так как почти все они связаны с
проверкой типов тех или иных параметров.
6. Результаты
Приведенные в статье методики анализа кода реализованы в коммерческом статическом
анализаторе кода Viva64. Использование этого анализатора на реальных проектах показало
целесообразность проверки кода при разработке 64-битных приложений: реальные ошибки в
коде с его помощью можно обнаружить значительно быстрее, чем при простом просмотре
исходных кодов.


Библиографический список
   1. J. P. Mueller. "24 Considerations for Moving Your Application to a 64-bit Platform", DevX.com,
      June 30, 2006.
   2. Hewlett-Packard, "Transitioning C and C++ programs to the 64-bit data model".
   3. S. Sokolov, "Bulletproofing C++ Code", Dr. Dobb's Journal, January 09, 2007.
   4. S. Meyers, M. Klaus, "A First Look at C++ Program Analyzer", Dr. Dobb's Journal, Feb. Issue,
      1997.

Más contenido relacionado

La actualidad más candente

Безопасность 64-битного кода
Безопасность 64-битного кодаБезопасность 64-битного кода
Безопасность 64-битного кодаTatyanazaxarova
 
Урок 7. Проблемы выявления 64-битных ошибок
Урок 7. Проблемы выявления 64-битных ошибокУрок 7. Проблемы выявления 64-битных ошибок
Урок 7. Проблемы выявления 64-битных ошибокTatyanazaxarova
 
Константин Книжник: статический анализ, взгляд со стороны
Константин Книжник: статический анализ, взгляд со стороныКонстантин Книжник: статический анализ, взгляд со стороны
Константин Книжник: статический анализ, взгляд со стороныTatyanazaxarova
 
Трудности сравнения анализаторов кода или не забывайте об удобстве использования
Трудности сравнения анализаторов кода или не забывайте об удобстве использованияТрудности сравнения анализаторов кода или не забывайте об удобстве использования
Трудности сравнения анализаторов кода или не забывайте об удобстве использованияTatyanazaxarova
 
Как не подавиться большим старым проектом. Юрий Минаев ➠ CoreHard Autumn 2019
Как не подавиться большим старым проектом. Юрий Минаев ➠  CoreHard Autumn 2019Как не подавиться большим старым проектом. Юрий Минаев ➠  CoreHard Autumn 2019
Как не подавиться большим старым проектом. Юрий Минаев ➠ CoreHard Autumn 2019corehard_by
 
Тестирование параллельных программ
Тестирование параллельных программТестирование параллельных программ
Тестирование параллельных программTatyanazaxarova
 
Применение статического анализа кода в преподавании и в разработке свободного ПО
Применение статического анализа кода в преподавании и в разработке свободного ПОПрименение статического анализа кода в преподавании и в разработке свободного ПО
Применение статического анализа кода в преподавании и в разработке свободного ПОAndrey Karpov
 
Борьба с ошибками (TDD)
Борьба с ошибками (TDD)Борьба с ошибками (TDD)
Борьба с ошибками (TDD)Fedor Malyshkin
 
Отладка и оптимизация многопоточных OpenMP-программ
Отладка и оптимизация многопоточных OpenMP-программОтладка и оптимизация многопоточных OpenMP-программ
Отладка и оптимизация многопоточных OpenMP-программTatyanazaxarova
 
Поиск уязвимостей в программах с помощью анализаторов кода
Поиск уязвимостей в программах с помощью анализаторов кодаПоиск уязвимостей в программах с помощью анализаторов кода
Поиск уязвимостей в программах с помощью анализаторов кодаTatyanazaxarova
 
Использование анализатора PVS-Studio в процессе инкрементальной сборки в Micr...
Использование анализатора PVS-Studio в процессе инкрементальной сборки в Micr...Использование анализатора PVS-Studio в процессе инкрементальной сборки в Micr...
Использование анализатора PVS-Studio в процессе инкрементальной сборки в Micr...Tatyanazaxarova
 
Двухкратный публичный code review, Евгения Фирсова (Яндекс)
Двухкратный публичный code review, Евгения Фирсова (Яндекс)Двухкратный публичный code review, Евгения Фирсова (Яндекс)
Двухкратный публичный code review, Евгения Фирсова (Яндекс)Ontico
 
СОВМЕСТНОЕ ПРИМЕНЕНИЕ КОНТРАКТОВ И ВЕРИФИКАЦИИ ДЛЯ ПОВЫШЕНИЯ КАЧЕСТВА АВТОМАТ...
СОВМЕСТНОЕ ПРИМЕНЕНИЕ КОНТРАКТОВ И ВЕРИФИКАЦИИ ДЛЯ ПОВЫШЕНИЯ КАЧЕСТВА АВТОМАТ...СОВМЕСТНОЕ ПРИМЕНЕНИЕ КОНТРАКТОВ И ВЕРИФИКАЦИИ ДЛЯ ПОВЫШЕНИЯ КАЧЕСТВА АВТОМАТ...
СОВМЕСТНОЕ ПРИМЕНЕНИЕ КОНТРАКТОВ И ВЕРИФИКАЦИИ ДЛЯ ПОВЫШЕНИЯ КАЧЕСТВА АВТОМАТ...ITMO University
 
Способы расширения зоны влияния вашей системы автотестов
Способы расширения зоны влияния вашей системы автотестовСпособы расширения зоны влияния вашей системы автотестов
Способы расширения зоны влияния вашей системы автотестовSQALab
 
Сравнение PVS-Studio с другими анализаторами кода
Сравнение PVS-Studio с другими анализаторами кодаСравнение PVS-Studio с другими анализаторами кода
Сравнение PVS-Studio с другими анализаторами кодаTatyanazaxarova
 
Php unit тесты с codeception
Php unit тесты с codeceptionPhp unit тесты с codeception
Php unit тесты с codeceptionVladislav Alexeyev
 

La actualidad más candente (20)

Безопасность 64-битного кода
Безопасность 64-битного кодаБезопасность 64-битного кода
Безопасность 64-битного кода
 
Урок 7. Проблемы выявления 64-битных ошибок
Урок 7. Проблемы выявления 64-битных ошибокУрок 7. Проблемы выявления 64-битных ошибок
Урок 7. Проблемы выявления 64-битных ошибок
 
Константин Книжник: статический анализ, взгляд со стороны
Константин Книжник: статический анализ, взгляд со стороныКонстантин Книжник: статический анализ, взгляд со стороны
Константин Книжник: статический анализ, взгляд со стороны
 
Трудности сравнения анализаторов кода или не забывайте об удобстве использования
Трудности сравнения анализаторов кода или не забывайте об удобстве использованияТрудности сравнения анализаторов кода или не забывайте об удобстве использования
Трудности сравнения анализаторов кода или не забывайте об удобстве использования
 
Net framework
Net frameworkNet framework
Net framework
 
Как не подавиться большим старым проектом. Юрий Минаев ➠ CoreHard Autumn 2019
Как не подавиться большим старым проектом. Юрий Минаев ➠  CoreHard Autumn 2019Как не подавиться большим старым проектом. Юрий Минаев ➠  CoreHard Autumn 2019
Как не подавиться большим старым проектом. Юрий Минаев ➠ CoreHard Autumn 2019
 
Тестирование параллельных программ
Тестирование параллельных программТестирование параллельных программ
Тестирование параллельных программ
 
Применение статического анализа кода в преподавании и в разработке свободного ПО
Применение статического анализа кода в преподавании и в разработке свободного ПОПрименение статического анализа кода в преподавании и в разработке свободного ПО
Применение статического анализа кода в преподавании и в разработке свободного ПО
 
Борьба с ошибками (TDD)
Борьба с ошибками (TDD)Борьба с ошибками (TDD)
Борьба с ошибками (TDD)
 
Отладка и оптимизация многопоточных OpenMP-программ
Отладка и оптимизация многопоточных OpenMP-программОтладка и оптимизация многопоточных OpenMP-программ
Отладка и оптимизация многопоточных OpenMP-программ
 
Поиск уязвимостей в программах с помощью анализаторов кода
Поиск уязвимостей в программах с помощью анализаторов кодаПоиск уязвимостей в программах с помощью анализаторов кода
Поиск уязвимостей в программах с помощью анализаторов кода
 
Использование анализатора PVS-Studio в процессе инкрементальной сборки в Micr...
Использование анализатора PVS-Studio в процессе инкрементальной сборки в Micr...Использование анализатора PVS-Studio в процессе инкрементальной сборки в Micr...
Использование анализатора PVS-Studio в процессе инкрементальной сборки в Micr...
 
Двухкратный публичный code review, Евгения Фирсова (Яндекс)
Двухкратный публичный code review, Евгения Фирсова (Яндекс)Двухкратный публичный code review, Евгения Фирсова (Яндекс)
Двухкратный публичный code review, Евгения Фирсова (Яндекс)
 
Фишки и прелести TypeScript
Фишки и прелести TypeScriptФишки и прелести TypeScript
Фишки и прелести TypeScript
 
СОВМЕСТНОЕ ПРИМЕНЕНИЕ КОНТРАКТОВ И ВЕРИФИКАЦИИ ДЛЯ ПОВЫШЕНИЯ КАЧЕСТВА АВТОМАТ...
СОВМЕСТНОЕ ПРИМЕНЕНИЕ КОНТРАКТОВ И ВЕРИФИКАЦИИ ДЛЯ ПОВЫШЕНИЯ КАЧЕСТВА АВТОМАТ...СОВМЕСТНОЕ ПРИМЕНЕНИЕ КОНТРАКТОВ И ВЕРИФИКАЦИИ ДЛЯ ПОВЫШЕНИЯ КАЧЕСТВА АВТОМАТ...
СОВМЕСТНОЕ ПРИМЕНЕНИЕ КОНТРАКТОВ И ВЕРИФИКАЦИИ ДЛЯ ПОВЫШЕНИЯ КАЧЕСТВА АВТОМАТ...
 
TAP
TAPTAP
TAP
 
Способы расширения зоны влияния вашей системы автотестов
Способы расширения зоны влияния вашей системы автотестовСпособы расширения зоны влияния вашей системы автотестов
Способы расширения зоны влияния вашей системы автотестов
 
лекция 3
лекция 3лекция 3
лекция 3
 
Сравнение PVS-Studio с другими анализаторами кода
Сравнение PVS-Studio с другими анализаторами кодаСравнение PVS-Studio с другими анализаторами кода
Сравнение PVS-Studio с другими анализаторами кода
 
Php unit тесты с codeception
Php unit тесты с codeceptionPhp unit тесты с codeception
Php unit тесты с codeception
 

Similar a Статический анализ кода для верификации 64-битных приложений

Сравнение диагностических возможностей анализаторов при проверке 64-битного кода
Сравнение диагностических возможностей анализаторов при проверке 64-битного кодаСравнение диагностических возможностей анализаторов при проверке 64-битного кода
Сравнение диагностических возможностей анализаторов при проверке 64-битного кодаTatyanazaxarova
 
20 ловушек переноса Си++ - кода на 64-битную платформу
20 ловушек переноса Си++ - кода на 64-битную платформу20 ловушек переноса Си++ - кода на 64-битную платформу
20 ловушек переноса Си++ - кода на 64-битную платформуTatyanazaxarova
 
Поиск ловушек в Си/Си++ коде при переносе приложений под 64-битную версию Win...
Поиск ловушек в Си/Си++ коде при переносе приложений под 64-битную версию Win...Поиск ловушек в Си/Си++ коде при переносе приложений под 64-битную версию Win...
Поиск ловушек в Си/Си++ коде при переносе приложений под 64-битную версию Win...Tatyanazaxarova
 
Разработка статического анализатора кода для обнаружения ошибок переноса прог...
Разработка статического анализатора кода для обнаружения ошибок переноса прог...Разработка статического анализатора кода для обнаружения ошибок переноса прог...
Разработка статического анализатора кода для обнаружения ошибок переноса прог...Tatyanazaxarova
 
Урок 26. Оптимизация 64-битных программ
Урок 26. Оптимизация 64-битных программУрок 26. Оптимизация 64-битных программ
Урок 26. Оптимизация 64-битных программTatyanazaxarova
 
7 шагов по переносу программы на 64-битную систему
7 шагов по переносу программы на 64-битную систему7 шагов по переносу программы на 64-битную систему
7 шагов по переносу программы на 64-битную системуTatyanazaxarova
 
Урок 6. Ошибки в 64-битном коде
Урок 6. Ошибки в 64-битном кодеУрок 6. Ошибки в 64-битном коде
Урок 6. Ошибки в 64-битном кодеTatyanazaxarova
 
Особенности разработки 64-битных приложений
Особенности разработки 64-битных приложенийОсобенности разработки 64-битных приложений
Особенности разработки 64-битных приложенийTatyanazaxarova
 
Проблемы тестирования 64-битных приложений
Проблемы тестирования 64-битных приложенийПроблемы тестирования 64-битных приложений
Проблемы тестирования 64-битных приложенийTatyanazaxarova
 
64 бита для Си++ программистов: от /Wp64 к Viva64
64 бита для Си++ программистов: от /Wp64 к Viva6464 бита для Си++ программистов: от /Wp64 к Viva64
64 бита для Си++ программистов: от /Wp64 к Viva64Tatyanazaxarova
 
Урок 5. Сборка 64-битного приложения
Урок 5. Сборка 64-битного приложенияУрок 5. Сборка 64-битного приложения
Урок 5. Сборка 64-битного приложенияTatyanazaxarova
 
Урок 19. Паттерн 11. Сериализация и обмен данными
Урок 19. Паттерн 11. Сериализация и обмен даннымиУрок 19. Паттерн 11. Сериализация и обмен данными
Урок 19. Паттерн 11. Сериализация и обмен даннымиTatyanazaxarova
 
Оптимизация 64-битных программ
Оптимизация 64-битных программОптимизация 64-битных программ
Оптимизация 64-битных программTatyanazaxarova
 
PVS-Studio, решение для разработки современных ресурсоемких приложений
PVS-Studio, решение для разработки современных ресурсоемких приложенийPVS-Studio, решение для разработки современных ресурсоемких приложений
PVS-Studio, решение для разработки современных ресурсоемких приложенийTatyanazaxarova
 
Разработка ресурсоемких приложений в среде Visual C++
Разработка ресурсоемких приложений в среде Visual C++Разработка ресурсоемких приложений в среде Visual C++
Разработка ресурсоемких приложений в среде Visual C++Tatyanazaxarova
 
Облегчаем процесс разработки с помощью статического анализа кода: Наш опыт
Облегчаем процесс разработки с помощью статического анализа кода: Наш опытОблегчаем процесс разработки с помощью статического анализа кода: Наш опыт
Облегчаем процесс разработки с помощью статического анализа кода: Наш опытAndrey Karpov
 
Урок 15. Паттерн 7. Упаковка указателей
Урок 15. Паттерн 7. Упаковка указателейУрок 15. Паттерн 7. Упаковка указателей
Урок 15. Паттерн 7. Упаковка указателейTatyanazaxarova
 
Разница в подходах анализа кода компилятором и выделенным инструментом
Разница в подходах анализа кода компилятором и выделенным инструментомРазница в подходах анализа кода компилятором и выделенным инструментом
Разница в подходах анализа кода компилятором и выделенным инструментомTatyanazaxarova
 
Как мы тестируем анализатор кода
Как мы тестируем анализатор кодаКак мы тестируем анализатор кода
Как мы тестируем анализатор кодаTatyanazaxarova
 
Что такое size_t и ptrdiff_t
Что такое size_t и ptrdiff_tЧто такое size_t и ptrdiff_t
Что такое size_t и ptrdiff_tTatyanazaxarova
 

Similar a Статический анализ кода для верификации 64-битных приложений (20)

Сравнение диагностических возможностей анализаторов при проверке 64-битного кода
Сравнение диагностических возможностей анализаторов при проверке 64-битного кодаСравнение диагностических возможностей анализаторов при проверке 64-битного кода
Сравнение диагностических возможностей анализаторов при проверке 64-битного кода
 
20 ловушек переноса Си++ - кода на 64-битную платформу
20 ловушек переноса Си++ - кода на 64-битную платформу20 ловушек переноса Си++ - кода на 64-битную платформу
20 ловушек переноса Си++ - кода на 64-битную платформу
 
Поиск ловушек в Си/Си++ коде при переносе приложений под 64-битную версию Win...
Поиск ловушек в Си/Си++ коде при переносе приложений под 64-битную версию Win...Поиск ловушек в Си/Си++ коде при переносе приложений под 64-битную версию Win...
Поиск ловушек в Си/Си++ коде при переносе приложений под 64-битную версию Win...
 
Разработка статического анализатора кода для обнаружения ошибок переноса прог...
Разработка статического анализатора кода для обнаружения ошибок переноса прог...Разработка статического анализатора кода для обнаружения ошибок переноса прог...
Разработка статического анализатора кода для обнаружения ошибок переноса прог...
 
Урок 26. Оптимизация 64-битных программ
Урок 26. Оптимизация 64-битных программУрок 26. Оптимизация 64-битных программ
Урок 26. Оптимизация 64-битных программ
 
7 шагов по переносу программы на 64-битную систему
7 шагов по переносу программы на 64-битную систему7 шагов по переносу программы на 64-битную систему
7 шагов по переносу программы на 64-битную систему
 
Урок 6. Ошибки в 64-битном коде
Урок 6. Ошибки в 64-битном кодеУрок 6. Ошибки в 64-битном коде
Урок 6. Ошибки в 64-битном коде
 
Особенности разработки 64-битных приложений
Особенности разработки 64-битных приложенийОсобенности разработки 64-битных приложений
Особенности разработки 64-битных приложений
 
Проблемы тестирования 64-битных приложений
Проблемы тестирования 64-битных приложенийПроблемы тестирования 64-битных приложений
Проблемы тестирования 64-битных приложений
 
64 бита для Си++ программистов: от /Wp64 к Viva64
64 бита для Си++ программистов: от /Wp64 к Viva6464 бита для Си++ программистов: от /Wp64 к Viva64
64 бита для Си++ программистов: от /Wp64 к Viva64
 
Урок 5. Сборка 64-битного приложения
Урок 5. Сборка 64-битного приложенияУрок 5. Сборка 64-битного приложения
Урок 5. Сборка 64-битного приложения
 
Урок 19. Паттерн 11. Сериализация и обмен данными
Урок 19. Паттерн 11. Сериализация и обмен даннымиУрок 19. Паттерн 11. Сериализация и обмен данными
Урок 19. Паттерн 11. Сериализация и обмен данными
 
Оптимизация 64-битных программ
Оптимизация 64-битных программОптимизация 64-битных программ
Оптимизация 64-битных программ
 
PVS-Studio, решение для разработки современных ресурсоемких приложений
PVS-Studio, решение для разработки современных ресурсоемких приложенийPVS-Studio, решение для разработки современных ресурсоемких приложений
PVS-Studio, решение для разработки современных ресурсоемких приложений
 
Разработка ресурсоемких приложений в среде Visual C++
Разработка ресурсоемких приложений в среде Visual C++Разработка ресурсоемких приложений в среде Visual C++
Разработка ресурсоемких приложений в среде Visual C++
 
Облегчаем процесс разработки с помощью статического анализа кода: Наш опыт
Облегчаем процесс разработки с помощью статического анализа кода: Наш опытОблегчаем процесс разработки с помощью статического анализа кода: Наш опыт
Облегчаем процесс разработки с помощью статического анализа кода: Наш опыт
 
Урок 15. Паттерн 7. Упаковка указателей
Урок 15. Паттерн 7. Упаковка указателейУрок 15. Паттерн 7. Упаковка указателей
Урок 15. Паттерн 7. Упаковка указателей
 
Разница в подходах анализа кода компилятором и выделенным инструментом
Разница в подходах анализа кода компилятором и выделенным инструментомРазница в подходах анализа кода компилятором и выделенным инструментом
Разница в подходах анализа кода компилятором и выделенным инструментом
 
Как мы тестируем анализатор кода
Как мы тестируем анализатор кодаКак мы тестируем анализатор кода
Как мы тестируем анализатор кода
 
Что такое size_t и ptrdiff_t
Что такое size_t и ptrdiff_tЧто такое size_t и ptrdiff_t
Что такое size_t и ptrdiff_t
 

Más de Tatyanazaxarova

Урок 27. Особенности создания инсталляторов для 64-битного окружения
Урок 27. Особенности создания инсталляторов для 64-битного окруженияУрок 27. Особенности создания инсталляторов для 64-битного окружения
Урок 27. Особенности создания инсталляторов для 64-битного окруженияTatyanazaxarova
 
Урок 25. Практическое знакомство с паттернами 64-битных ошибок
Урок 25. Практическое знакомство с паттернами 64-битных ошибокУрок 25. Практическое знакомство с паттернами 64-битных ошибок
Урок 25. Практическое знакомство с паттернами 64-битных ошибокTatyanazaxarova
 
Урок 24. Фантомные ошибки
Урок 24. Фантомные ошибкиУрок 24. Фантомные ошибки
Урок 24. Фантомные ошибкиTatyanazaxarova
 
Урок 23. Паттерн 15. Рост размеров структур
Урок 23. Паттерн 15. Рост размеров структурУрок 23. Паттерн 15. Рост размеров структур
Урок 23. Паттерн 15. Рост размеров структурTatyanazaxarova
 
Урок 21. Паттерн 13. Выравнивание данных
Урок 21. Паттерн 13. Выравнивание данныхУрок 21. Паттерн 13. Выравнивание данных
Урок 21. Паттерн 13. Выравнивание данныхTatyanazaxarova
 
Урок 20. Паттерн 12. Исключения
Урок 20. Паттерн 12. ИсключенияУрок 20. Паттерн 12. Исключения
Урок 20. Паттерн 12. ИсключенияTatyanazaxarova
 
Урок 17. Паттерн 9. Смешанная арифметика
Урок 17. Паттерн 9. Смешанная арифметикаУрок 17. Паттерн 9. Смешанная арифметика
Урок 17. Паттерн 9. Смешанная арифметикаTatyanazaxarova
 
Урок 16. Паттерн 8. Memsize-типы в объединениях
Урок 16. Паттерн 8. Memsize-типы в объединенияхУрок 16. Паттерн 8. Memsize-типы в объединениях
Урок 16. Паттерн 8. Memsize-типы в объединенияхTatyanazaxarova
 
Урок 13. Паттерн 5. Адресная арифметика
Урок 13. Паттерн 5. Адресная арифметикаУрок 13. Паттерн 5. Адресная арифметика
Урок 13. Паттерн 5. Адресная арифметикаTatyanazaxarova
 
Урок 11. Паттерн 3. Операции сдвига
Урок 11. Паттерн 3. Операции сдвигаУрок 11. Паттерн 3. Операции сдвига
Урок 11. Паттерн 3. Операции сдвигаTatyanazaxarova
 
Урок 10. Паттерн 2. Функции с переменным количеством аргументов
Урок 10. Паттерн 2. Функции с переменным количеством аргументовУрок 10. Паттерн 2. Функции с переменным количеством аргументов
Урок 10. Паттерн 2. Функции с переменным количеством аргументовTatyanazaxarova
 
Урок 9. Паттерн 1. Магические числа
Урок 9. Паттерн 1. Магические числаУрок 9. Паттерн 1. Магические числа
Урок 9. Паттерн 1. Магические числаTatyanazaxarova
 
Урок 4. Создание 64-битной конфигурации
Урок 4. Создание 64-битной конфигурацииУрок 4. Создание 64-битной конфигурации
Урок 4. Создание 64-битной конфигурацииTatyanazaxarova
 
Статический анализ Си++ кода
Статический анализ Си++ кодаСтатический анализ Си++ кода
Статический анализ Си++ кодаTatyanazaxarova
 
PVS-Studio научился следить за тем, как вы программируете
PVS-Studio научился следить за тем, как вы программируетеPVS-Studio научился следить за тем, как вы программируете
PVS-Studio научился следить за тем, как вы программируетеTatyanazaxarova
 
Пояснения к статье про Copy-Paste
Пояснения к статье про Copy-PasteПояснения к статье про Copy-Paste
Пояснения к статье про Copy-PasteTatyanazaxarova
 
Статический анализ и ROI
Статический анализ и ROIСтатический анализ и ROI
Статический анализ и ROITatyanazaxarova
 
Вечный вопрос измерения времени
Вечный вопрос измерения времениВечный вопрос измерения времени
Вечный вопрос измерения времениTatyanazaxarova
 
По колено в Си++ г... коде
По колено в Си++ г... кодеПо колено в Си++ г... коде
По колено в Си++ г... кодеTatyanazaxarova
 

Más de Tatyanazaxarova (20)

Урок 27. Особенности создания инсталляторов для 64-битного окружения
Урок 27. Особенности создания инсталляторов для 64-битного окруженияУрок 27. Особенности создания инсталляторов для 64-битного окружения
Урок 27. Особенности создания инсталляторов для 64-битного окружения
 
Урок 25. Практическое знакомство с паттернами 64-битных ошибок
Урок 25. Практическое знакомство с паттернами 64-битных ошибокУрок 25. Практическое знакомство с паттернами 64-битных ошибок
Урок 25. Практическое знакомство с паттернами 64-битных ошибок
 
Урок 24. Фантомные ошибки
Урок 24. Фантомные ошибкиУрок 24. Фантомные ошибки
Урок 24. Фантомные ошибки
 
Урок 23. Паттерн 15. Рост размеров структур
Урок 23. Паттерн 15. Рост размеров структурУрок 23. Паттерн 15. Рост размеров структур
Урок 23. Паттерн 15. Рост размеров структур
 
Урок 21. Паттерн 13. Выравнивание данных
Урок 21. Паттерн 13. Выравнивание данныхУрок 21. Паттерн 13. Выравнивание данных
Урок 21. Паттерн 13. Выравнивание данных
 
Урок 20. Паттерн 12. Исключения
Урок 20. Паттерн 12. ИсключенияУрок 20. Паттерн 12. Исключения
Урок 20. Паттерн 12. Исключения
 
Урок 17. Паттерн 9. Смешанная арифметика
Урок 17. Паттерн 9. Смешанная арифметикаУрок 17. Паттерн 9. Смешанная арифметика
Урок 17. Паттерн 9. Смешанная арифметика
 
Урок 16. Паттерн 8. Memsize-типы в объединениях
Урок 16. Паттерн 8. Memsize-типы в объединенияхУрок 16. Паттерн 8. Memsize-типы в объединениях
Урок 16. Паттерн 8. Memsize-типы в объединениях
 
Урок 13. Паттерн 5. Адресная арифметика
Урок 13. Паттерн 5. Адресная арифметикаУрок 13. Паттерн 5. Адресная арифметика
Урок 13. Паттерн 5. Адресная арифметика
 
Урок 11. Паттерн 3. Операции сдвига
Урок 11. Паттерн 3. Операции сдвигаУрок 11. Паттерн 3. Операции сдвига
Урок 11. Паттерн 3. Операции сдвига
 
Урок 10. Паттерн 2. Функции с переменным количеством аргументов
Урок 10. Паттерн 2. Функции с переменным количеством аргументовУрок 10. Паттерн 2. Функции с переменным количеством аргументов
Урок 10. Паттерн 2. Функции с переменным количеством аргументов
 
Урок 9. Паттерн 1. Магические числа
Урок 9. Паттерн 1. Магические числаУрок 9. Паттерн 1. Магические числа
Урок 9. Паттерн 1. Магические числа
 
Урок 4. Создание 64-битной конфигурации
Урок 4. Создание 64-битной конфигурацииУрок 4. Создание 64-битной конфигурации
Урок 4. Создание 64-битной конфигурации
 
Статический анализ Си++ кода
Статический анализ Си++ кодаСтатический анализ Си++ кода
Статический анализ Си++ кода
 
PVS-Studio
PVS-Studio PVS-Studio
PVS-Studio
 
PVS-Studio научился следить за тем, как вы программируете
PVS-Studio научился следить за тем, как вы программируетеPVS-Studio научился следить за тем, как вы программируете
PVS-Studio научился следить за тем, как вы программируете
 
Пояснения к статье про Copy-Paste
Пояснения к статье про Copy-PasteПояснения к статье про Copy-Paste
Пояснения к статье про Copy-Paste
 
Статический анализ и ROI
Статический анализ и ROIСтатический анализ и ROI
Статический анализ и ROI
 
Вечный вопрос измерения времени
Вечный вопрос измерения времениВечный вопрос измерения времени
Вечный вопрос измерения времени
 
По колено в Си++ г... коде
По колено в Си++ г... кодеПо колено в Си++ г... коде
По колено в Си++ г... коде
 

Статический анализ кода для верификации 64-битных приложений

  • 1. Статический анализ кода для верификации 64-битных приложений Авторы: Андрей Карпов, Евгений Рыжков Дата: 22.04.2007 Аннотация В результате появления на рынке персональных компьютеров 64-битных процессоров, перед разработчиками программ возникает задача переноса старых 32-битных приложений на новую платформу. После такого переноса кода приложение может вести себя некорректно. В статье рассматривается вопрос разработки и применения статического анализатора кода для проверки правильности таких приложений. Приводятся проблемы, возникающие в приложениях после перекомпиляции для 64-битных систем, а также правила, по которым выполняется проверка кода. Данная статья содержит различные примеры 64-битных ошибок. Однако с момента ее написания, мы узнали значительно больше примеров и типов ошибок, которые не описаны в этой статье. Мы предлагаем вам познакомиться со статьей "Коллекция примеров 64-битных ошибок в реальных программах", в которой наиболее полно описаны известные нам дефекты в 64-битных программах. Также рекомендуем изучить "Уроки разработки 64-битных приложений на языке Си/Си++", где описана методика создания корректного 64-битного кода и методы поиска всех видов дефектов с использованием анализатора кода Viva64. 1. Введение Массовое производство и повсеместная доступность 64-битных процессоров привели разработчиков приложений к необходимости разработки 64-битных версий своих программ. Ведь для того, чтобы пользователи могли получить реальные преимущества от использования новых процессоров, приложения должны быть перекомпилированы для поддержки 64-битной архитектуры. Теоретически этот процесс не должен представлять проблем. Однако на практике часто после перекомпиляции приложение работает не так, как должно. Это может проявляться самым широким образом: от порчи файлов с данными, до отказа работы справочной системы. Причина такого поведения кроется в изменении размеров базовых типов данных в 64-битных процессорах, а точнее - в изменении соотношений между типами. Именно поэтому основные проблемы при переносе кода обнаруживаются в приложениях, разработанных с использованием низкоуровневых языков программирования типа C или C++. В языках с четко структурированной системой типов (например, языки .NET Framework), как правило, таких проблем не возникает. В чем же проблема именно с низкоуровневыми языками? Дело в том, что даже все высокоуровневые конструкции и библиотеки C++ в конечном итоге реализованы с использованием низкоуровневых типов данных, таких как указатель, машинное слово и т.п. Поскольку при изменении архитектуры эти типы данных меняются, то и поведение программ также может измениться. Для того чтобы быть уверенным в корректности программы на новой платформе, необходимо вручную выполнить просмотр кода и убедиться в его корректности. Однако, конечно же,
  • 2. выполнить полный просмотр кода всего реального коммерческого приложения невозможно ввиду его огромного размера. Поэтому возникает задача поиска в исходном коде программы тех мест, которые при переносе с 32-битной на 64-битную архитектуру могут работать неправильно. Решению такой задачи и посвящена настоящая статья. 2. Примеры проблем, возникающих при переносе кода на 64-битные системы Приведем несколько примеров, когда после переноса кода на 64-битную систему, в приложении могут проявиться новые ошибки. Другие примеры можно найти в различных статьях [1, 2]. При расчете необходимой для массива памяти использовался явно размер типа элементов. На 64- битной системе этот размер изменился, но код остался прежним: size_t ArraySize = N * 4; intptr_t *Array = (intptr_t *)malloc(ArraySize); Некоторая функция возвращала значение -1 типа size_t в случае ошибки. Проверка результата была записана так: size_t result = func(); if (result == 0xffffffffu) { // error } На 64-битной системе значение -1 для этого типа выглядит уже по-другому и проверка не срабатывает. Арифметика с указателями - постоянный источник проблем. Но в случае с 64-битными приложениями к уже известным добавляются новые проблемы. Рассмотрим пример: unsigned a16, b16, c16; char *pointer; ... pointer += a16 * b16 * c16; Как видно из примера, указатель никогда не сможет получить приращение больше 4 гигабайт, что хоть и не диагностируется современными компиляторами как ошибка, но приведет в будущем к неработающим программам. Можно привести значительно больше примеров потенциально опасного кода. Все эти и многие другие ошибки были обнаружены в реальных приложениях во время переноса их на 64-битную платформу.
  • 3. 3. Обзор существующих решений Существуют различные подходы к обеспечению корректности кода приложений. Перечислим наиболее распространенные из них: тестирование с помощью юнит-тестов, динамический анализ кода (во время работы приложения), статический анализ кода (анализ исходных текстов). Нельзя сказать, что какой-то один вариант тестирования лучше других - все эти подходы обеспечивают различные аспекты качества приложений. Юнит-тесты предназначены для быстрой проверки небольших участков кода, например, отдельных функций и классов [3]. Их особенность в том, что эти тесты выполняются быстро и допускают частый запуск. Из этого вытекают два нюанса использования такой технологии. Во- первых, эти тесты должны быть написаны. Во-вторых, тестирование выделения больших объемов памяти (например, более двух гигабайт) занимает значительное время, поэтому нецелесообразно, так как юнит-тесты должны отрабатываться быстро. Динамические анализаторы кода (лучший представитель - это Compuware BoundsChecker) предназначены для обнаружения ошибок в приложении во время выполнения программы. Из этого принципа работы и вытекает основной недостаток динамического анализатора. Для того, чтобы убедиться в корректности программы, необходимо выполнить все возможные ветки кода. Для реальной программы это может быть затруднительно. Но это не значит, что динамический анализ кода не нужен. Такой анализ позволяет обнаружить ошибки, которые зависят от действий пользователя и не могут быть определены по коду приложения. Статические анализаторы кода (как, например, Gimpel Software PC-lint и Parasoft C++test) предназначены для комплексного обеспечения качества кода и содержат несколько сотен анализируемых правил [4]. В них также есть некоторые из правил, анализирующих корректность 64-битных приложений. Однако, поскольку это анализаторы кода общего назначения, то их использование для обеспечения качества 64-битных приложений не всегда удобно. Это объясняется, прежде всего, тем, что они не предназначены именно для этой цели. Другим серьезным недостатком является их ориентированность на модель данных, используемую в Unix- системах (LP64). В то время как модель данных, используемая в Windows-системах (LLP64), существенно отличается от нее. Поэтому применение этих статических анализаторов для проверки 64-битных Windows-приложений возможно только после неочевидной дополнительной настройки. Некоторым дополнительным уровнем проверки кода можно считать наличие в компиляторах специальной диагностики потенциально некорректного кода (например, ключ /Wp64 в компиляторе Microsoft Visual C++). Однако этот ключ позволяет отследить лишь наиболее некорректные конструкции, в то время как многие из также опасных операций он пропускает. Возникает вопрос: "Может быть, проверка кода приложений при переносе на 64-битные системы не нужна, поскольку таких ошибок в приложении будет не так много?". Мы считаем, что такая проверка необходима хотя бы потому, что крупнейшие компании (например, IBM и Hewlett- Packard) разместили на своих сайтах статьи [2], посвященные возникающим при переносе кода ошибкам. 4. Правила анализа корректности кода Мы сформулировали 10 правил поиска опасных конструкций языка C++ с точки зрения переноса кода на 64-битную систему. Перед описанием правил необходимо напомнить о понятии значащих
  • 4. бит. Говоря о количестве значащих бит, мы учитываем, что отрицательные значения используют все биты данного типа: int a = 1; // Используется 1 бит. (0x00000001) int b = -1; // Используется 32 бита. (0xFFFFFFFF) В правилах используется специально введенный тип memsize. Под memsize-типом мы будем понимать любой простой целочисленный тип, способный хранить в себе указатель и меняющий свою размерность при изменении разрядности платформы с 32 бит на 64 бита. Примеры memsize- типов: size_t, ptrdiff_t, все указатели, intptr_t, INT_PTR, DWORD_PTR. Теперь перечислим сами правила и приведем примеры их применения. ПРАВИЛО 1. Следует считать опасными конструкции явного и неявного приведения целых типов размерностью 32 бита к memsize типам: unsigned a; size_t b = a; array[a] = 1; Исключения: 1) Приводимый 32-битный целый тип является результатом выражения, где для представления значения выражения требуется меньше 32 бит: unsigned short a; unsigned char b; size_t c = a * b; При этом выражение не должно состоять только из числовых литералов: size_t a = 100 * 100 * 100; 2) Приводимый 32-битный тип представлен числовым литералом: size_t a = 1; size_t b = 'G'; ПРАВИЛО 2. Следует считать опасными конструкции явного и неявного приведения memsize-типов к целым типам размерностью 32 бита: size_t a; unsigned b = a; Исключение: Приводимый тип size_t является результатом выполнения оператора sizeof():
  • 5. int a = sizeof(float); ПРАВИЛО 3. Опасной следует считать виртуальную функцию, удовлетворяющую ряду условий: а) функция объявлена в базовом классе и в классе-потомке. б) типы аргументов функций не совпадают, но эквивалентны на 32-битной системе (например: unsigned, size_t) и не эквивалентны на 64-битной. class Base { virtual void foo(size_t); }; class Derive : public Base { virtual void foo(unsigned); }; ПРАВИЛО 4. Опасными следует считать вызовы перегруженных функций с аргументом типа memsize. При этом функции должны быть перегружены для целых 32-битных и 64-битных типов данных: void WriteValue(__int32); void WriteValue(__int64); ... ptrdiff_t value; WriteValue(value); ПРАВИЛО 5. Опасным следует считать явное приведение одного типа указателя к другому, если один из них ссылается на 32-х/64-x битный тип, а другой на memsize-тип: int *array; size_t *sizetPtr = (size_t *)(array); ПРАВИЛО 6. Опасным следует считать явные и неявные приведения memsize-типа к double и наоборот: size_t a; double b = a; ПРАВИЛО 7. Опасным следует считать передачу memsize-типа в функцию с переменным количеством аргументов:
  • 6. size_t a; printf("%u", a); ПРАВИЛО 8. Опасным следует считать использование ряда магических констант (4, 32, 0x7fffffff, 0x80000000, 0xffffffff): size_t values[ARRAY_SIZE]; memset(values, ARRAY_SIZE * 4, 0); ПРАВИЛО 9. Опасным следует считать наличие в объединениях (union) членов memsize-типов: union PtrNumUnion { char *m_p; unsigned m_n; } u; ... u.m_p = str; u.m_n += delta; ПРАВИЛО 10. Опасными следует считать генерацию и обработку исключений с использованием memsize-типов: char *p1, *p2; try { throw (p1 - p2); } catch (int) { ... } Необходимо заметить, что, например, под правило 1 попадает не только приведение типа во время присваивания, но также и при вызове функций, при индексации массивов, во время арифметики с указателями. Правила (как первое, так и другие) описывают большое количество ошибок, которое не ограничивается приведенными примерами. Другими словами, приведенные примеры лишь демонстрируют некоторые частные варианты применения правил. Представленные правила реализованы в статическом анализаторе кода Viva64. Принцип его работы рассматривается в следующем разделе.
  • 7. 5. Архитектура анализатора Работа анализатора состоит из нескольких этапов, часть из которых свойственна обычным компиляторам C++ (рисунок 1). Рисунок 1. Архитектура анализатора. На вход анализатора поступает файл с исходным кодом, а в результате его работы генерируется отчет о потенциальных ошибках в коде с номерами строк. Этапы работы анализатора: препроцессорная обработка, построение дерева кода и собственно анализ. На этапе препроцессорной обработки выполняется подключение файлов, объявленных с помощью #include-директив, а также обработка параметров условной компиляции (#ifdef/#endif). В результате разбора (parsing) файла полученного после препроцессорной обработки, строится дерево кода с той информацией, которая в дальнейшем необходима для анализа. Рассмотрим простой пример: int A, B; ptrdiff_t C; C = B * A; В этом коде есть потенциальная проблема, связанная с различными типами данных. Так, переменная C здесь никогда не сможет принять значение меньше или больше 2 Гигабайт, что может быть неправильно. Анализатор должен сообщить, что в строке "C = B * A" потенциально некорректная конструкция. Вариантов исправления этого кода несколько. Если переменные B и A никогда не могут принимать по смыслу значения больше 2 гигабайт, но переменная C может, то записать выражение следует так: C = (ptrdiff_t)(B) * (ptrdiff_t)(A); Но если переменные A и B на 64-битной системе могут принимать большие значение, то надо исправить их тип на ptrdiff_t: ptrdiff_t A; ptrdiff_t B; ptrdiff_t C;
  • 8. C = B * A; Покажем, как это выполняется на уровне анализа дерева кода. Сначала для кода строится дерево (рисунок 2). Рисунок 2. Дерево кода. Затем на этапе анализа дерева необходимо определить типы переменных, участвующих в вычислении выражения. Для этого используется вспомогательная информация, полученная во время построения дерева (модуль хранения типов), как показано на рисунке 3.
  • 9. Рисунок 3. Хранение информации о типах. После определения типов всех переменных, участвующих в выражении, необходимо вычислить результирующие типы подвыражений. В рассматриваемом примере необходимо определить тип результата промежуточного выражения "B * A". Это делается с помощью модуля вычисления типов как показано на рисунке 4. Рисунок 4. Вычисление типа выражений. Затем выполняется проверка при вычислении типа результирующего выражения (операция "=" в нашем примере) и в случае конфликта типов конструкция помечается как потенциально опасная. В рассматриваемом примере такой конфликт имеет место, так как переменная C имеет размер 64 бита (на 64-битной системе), а результат выражения "B * A" - 32 бита. Аналогичным образом выполняется анализ других правил, так как почти все они связаны с проверкой типов тех или иных параметров.
  • 10. 6. Результаты Приведенные в статье методики анализа кода реализованы в коммерческом статическом анализаторе кода Viva64. Использование этого анализатора на реальных проектах показало целесообразность проверки кода при разработке 64-битных приложений: реальные ошибки в коде с его помощью можно обнаружить значительно быстрее, чем при простом просмотре исходных кодов. Библиографический список 1. J. P. Mueller. "24 Considerations for Moving Your Application to a 64-bit Platform", DevX.com, June 30, 2006. 2. Hewlett-Packard, "Transitioning C and C++ programs to the 64-bit data model". 3. S. Sokolov, "Bulletproofing C++ Code", Dr. Dobb's Journal, January 09, 2007. 4. S. Meyers, M. Klaus, "A First Look at C++ Program Analyzer", Dr. Dobb's Journal, Feb. Issue, 1997.