Забытые проблемы разработки 64-битных программ
Введение
История развития 64-битных программных систем не нова и составляет уже более
десятилетия [1]. В 1991 году был выпушен первый 64-битный микропроцессор MIPS
R4000 [2, 3]. С тех пор в форумах и статьях возникали дискуссии посвященные
переносу программ на 64-битные системы. Началось обсуждения проблем связанных
с разработкой 64-битных программ на языке Си. Обсуждались вопросы, какая модель
данных лучше, что такое long long и многое другое. Вот, например, интересная
подборка сообщений [4] из новостной группы comp.lang.c, посвященная использованию
типа long long в языке Си, что в свою очередь было связано с появлением 64-битных
систем.
Одним из наиболее распространенных и чувствительных к изменению размерности
типов данных является язык Си. Из-за его низкоуровневых свойств, следует постоянно
контролировать корректность программы на этом языке, переносимой на новую платформу.
Естественно, что при появлении 64-битных систем разработчики по всему миру вновь
столкнулись с задачами обеспечения совместимости старого исходного кода с новыми
системами. Одним из косвенных свидетельств сложности проблем миграции является
большое количество моделей данных, которые постоянно следует учитывать. Модель
данных - это соотношение размеров базовых типов в языке программирования. На
рисунке 1 показаны размерность типов в различных моделях данных, на которые
мы в дальнейшем будем ссылаться.

Рисунок 1. Модели данных.
Существующие публикации и инструменты в сфере верификации 64-битных приложений
Конечно, это был не первый этап смены разрядности. Достаточно вспомнить переход
с 16-битных систем на 32-битные. Естественно накопленный опыт оказал свое положительное
воздействие на этапе перехода на 64-битные системы.
Но переход на 64-битные системы, имел свои нюансы, в результате чего появилась
серий исследований и публикаций по данным вопросам, например [5, 6, 7].
В основном авторами того времени, выделялись ошибки, следующих типов:
- Упаковка указателей в типы меньшей размерности. Например, помещение указателя
в тип int на системе с моделью данных LP64 приведет к обрезанию значения указателя
и невозможности его использовать в дальнейшем.
- Использование магических констант. Опасность заключается в использовании
таких чисел как 4, 32, 0x80000000 и ряда других вместо специализированных
констант или использования оператора sizeof().
- Некорректные операции сдвига, не учитывающих увеличение размерности ряда
типов.
- Использование некорректных объединений или структур без учета выравнивания
на системах с различной разрядностью.
- Ошибки работы с битовыми полями.
- Некорректные арифметические выражения. Пример:
int x = 100000, y = 100000, z = 100000;
long long s = x * y * x;
Рассматривались и некоторые другие более редкие ошибки, но основные перечислены
в списке.
На основе проведенных исследований вопроса верификации 64-битного кода были
предложены решения, обеспечивающих диагностику опасных конструкций. Например,
такую проверку реализовали в статических анализаторах Gimpel Software PC-Lint
(http://www.gimpel.com) и Parasoft C++test
(http://www.parasoft.com).
Возникает вопрос, если 64-битные системы существуют так давно, существуют статьи,
посвященные данной тематике и даже программные инструменты обеспечивающий контроль
опасных конструкций в коде, так стоит ли возвращаться к этому вопросу?
К сожалению да - стоит. Причиной служит прогресс, произошедший за эти годы
в области информационных технологий. А актуальность данного вопроса связана
с быстрым распространением 64-битных версий операционной системы Windows.
Существующая информационная поддержка и инструменты в области разработки 64-битных
технологий устарели и нуждаются в существенной переработке. Но Вы возразите,
что в Интернете можно найти множество современных статей (2005-2007г), посвященных
вопросам разработки 64-битных приложений на языке Си/Си++. К сожалению, на практике
они являются не более чем пересказом старых статей, применительно к новой 64-битной
версии Windows, без учета ее специфики и произошедших изменений технологий.
Неосвещенные проблемы разработки 64-битных программ.
Но начнем по порядку. Авторы новых статей не учитывают, огромный объем памяти,
который стал доступен современным приложениям. Конечно, указатели были 64-битными
еще в стародавние времена, но вот использовать таким программам массивы размером
в несколько гигабайт не доводилось. В результате, как в старых, так и в новых
статьях выпал целый пласт ошибок, связанный с ошибками индексации больших массивов.
Практически невозможно найти в статьях описание ошибки, подобной следующей:
for (int x = 0; x != width; ++x)
for (int y = 0; y != height; ++y)
for (int z = 0; z != depth; ++z)
BigArray[z * width * height + y * width + x] = InitValue;
В этом примере, выражение "z * width * height + y * width + x" используемое
для адресации имеет тип int, а, следовательно, данный код будет некорректен
на массивах, содержащих более 2 GB элементов. На 64-битных системах для безопасной
индексации к большим массивам следует использовать типы ptrdiff_t, size_t или
производные от них. Отсутствие описания такого вида ошибки в статьях объясняется
очень просто. Во времена их написания, машины с объемом памяти, позволяющей
хранить такие массивы, были практически не доступны. Сейчас же это становится
рядовой задачей в программировании и с большим удивлением можно наблюдать, как
код верой и правдой служивший многие годы, вдруг перестает корректно работать
при работе с большими массивами данных на 64-битных системах.
Другой пласт практически неосвященных проблем, представлен ошибками, связанными
с возможностями и особенностями языка Си++. Почему так произошло, тоже достаточно
объяснимо. Во время внедрения первых 64-битных систем, язык Си++ для них не
существовал или он был не распространен. По этому практически все статьи посвящены
проблемам в области языка Си. Современные авторы заменили название Си на Си/Си++,
но нового ничего не добавили.
Но отсутствие в статьях описания ошибок специфичных для Си++ не означает, что
их нет. Существуют ошибки, проявляющие себя при переносе программ на 64-битные
системы. Они связанны с виртуальными функциями, исключениями, перегруженными
функциями и так далее. Более подробно с такими ошибками можно ознакомиться в
статье [8]. Приведем простой пример, связанный с использованием виртуальных
функций.
class CWinApp {
...
virtual void WinHelp(DWORD_PTR dwData, UINT nCmd);
};
class CSampleApp : public CWinApp {
...
virtual void WinHelp(DWORD dwData, UINT nCmd);
};
Проследим жизненный цикл разработки некоторого приложения. Пусть первоначально
оно разрабатывалось под Microsoft Visual C++ 6.0. когда, функция WinHelp в классе
CWinApp имела следующий прототип:
virtual void WinHelp(DWORD dwData, UINT nCmd = HELP_CONTEXT);
Совершенно верно было осуществить перекрытие виртуальной функции в классе CSampleApp,
как показано в примере. Затем проект был перенесен в Microsoft Visual C++ 2005,
где прототип функции в классе CWinApp претерпел изменения, заключающиеся в смене
типа DWORD на тип DWORD_PTR. На 32-битной системе программа продолжит совершенно
корректно работать, так как здесь типы DWORD и DWORD_PTR совпадают. Неприятности
проявят себя при компиляции данного кода под 64-битную платформу. Получатся
две функции с одинаковыми именами, но с различными параметрами, в результате
чего перестанет вызываться пользовательский код.
Помимо особенностей разработки 64-битных программ с точки зрения языка Си++,
существуют и другие тонкие моменты. Например, особенности связанные с архитектурой
64-битной версии Windows. Хочется заранее предупредить разработчиков о потенциальных
проблемах и порекомендовать уделить большее внимание тестированию 64-битного
программного обеспечения [9].
Теперь вернемся к методам верификации исходного кода программы с использованием
статических анализаторов. Я думаю, вы уже угадали, что здесь тоже не все так
хорошо как кажется. Не смотря на заявленную поддержку диагностирования особенностей
64-битного кода, эта поддержка на данный момент не удовлетворяет необходимым
требованием. Причина заключена в том, что диагностические правила были созданы
по все тем же статьям, не учитывающим специфику языка Си++ или обработку больших
массивов данных, превышающих 2 GB.
Для Windows разработчиков дело обстоит еще несколько хуже. Основные статические
анализаторы рассчитаны на диагностику 64-битных ошибок для модели данных LP64,
в то время, как в Windows используется модель данных LLP64 [10]. Обусловлено
это тем, что 64-битные версии Windows молоды, а ранее 64-битные системы были
представлены Unix подобными системами с моделью данных LP64.
В качестве примера, рассмотрим диагностическое сообщение 3264bit_IntToLongPointerCast
(port-10), генерируемое анализатором Parasoft C++test.
int *intPointer;
long *longPointer;
longPointer = (long *)intPointer; //-ERR port-10
C++test предполагает, что с точки зрения модели LP64, данная конструкция будет
некорректна. Но в рамках модели данных принятых в Windows данная конструкция
будет безопасна.
Рекомендации по верификации 64-битных программ
Хорошо, скажите Вы, проблемы разработки 64-битных версий программ действительно
актуальны. Но как найти все эти ошибки?
Исчерпывающий ответ дать невозможно, но можно привести ряд рекомендаций, которые
в сумме позволят обеспечить безопасную миграцию на 64-битные системы и обеспечить
необходимый уровень надежности.
- Ознакомьте Ваших коллег, связанных с разработкой 64-битых приложений со
следующими статьями: [7, 8, 9, 10, 11, 12, 13, 14, 15].
- Ознакомьте Ваших коллег с методологией статического анализа кода: [16,
17, 18]. Статическая верификация кода один из лучших способов поиска такого
рода ошибок. Она позволяет убедиться в работоспособности даже тех частей кода,
работу которых на больших объемах данных сложно смоделировать в реальности,
например при использовании методологии юнит-тестов.
- Разработчикам будет полезно познакомиться со статическими анализаторами,
такими как Parasoft C++test (www.parasoft.com), Gimpel Software PC-lint (www.gimpel.com),
Abraxas Software CodeCheck (www.abxsoft.com).
- Для разработчиков Windows приложений особенно будет полезно познакомиться
со специализированным статическим анализатором Viva64 (http://www.viva64.com),
рассчитанным на модель данных LLP64 [19].
- Усовершенствуйте систему юнит-тестирования, включив в набор тестов обработку
больших массивов данных. Более подробно с необходимостью тестирования на большом
объеме данных, можно познакомиться в [9], а также узнать, как лучше организовать
такое тестирование.
- Провести тщательно ручное тестирование перенесенного кода на реальных больших
задачах, использующих возможности 64-битных систем. Смена архитектуры слишком
существенное изменение, чтобы полностью положиться на автоматизированные системы
тестирования.
Ресурсы.
- John R. Mashey, The Long Road to 64 Bits.
http://www.acmqueue.org/modules.php?name=Content&pa=showpage&pid=421&page=7
- Wikipedia: MIPS architecture.
http://en.wikipedia.org/wiki/MIPS_architecture
- John R. Mashey, 64 bit processors: history and rationale.
http://yarchive.net/comp/64bit.html
- John R. Mashey, The 64-bit integer type "long long": arguments
and history.
http://yarchive.net/comp/longlong.html
- 64-bit and Data Size Neutrality.
http://www.unix.org/whitepapers/64bit.html
- 64-Bit Programming Models: Why LP64?
http://www.unix.org/version2/whatsnew/lp64_wp.html
- Transitioning C and C++ programs to the 64-bit data model.
http://devresource.hp.com/drc/STK/docs/refs/64datamodel.jsp
- Andrey Karpov, Evgeniy Ryzhkov. 20 issues of porting C++ code on the 64-bit
platform.
http://www.viva64.com/articles/20_issues_of_porting_C++_code_on_the_64-bit_platform.html
Русская версия: http://www.devdoc.ru/index.php/content/view/x64porting.htm
- Andrey Karpov. Evgeniy Ryzhkov. Problems of testing 64-bit applications.
http://www.viva64.com/articles/Problems_of_testing_64-bit_applications.html
- The Old New Thing: Why did the Win64 team choose the LLP64 model?
http://blogs.msdn.com/oldnewthing/archive/2005/01/31/363790.aspx
- Brad Martin, Anita Rettinger, and Jasmit Singh. Multiplatform Porting to
64 Bits.
http://www.ddj.com/hpc-high-performance-computing/184406427
- Migrating 32-bit Managed Code to 64-bit.
http://msdn2.microsoft.com/en-us/library/ms973190.aspx
- Matt Pietrek. Everything You Need To Know To Start Programming 64-Bit Windows
Systems.
http://msdn.microsoft.com/msdnmag/issues/06/05/x64/default.aspx
- Microsoft Game Technology Group. 64-bit programming for Game Developers.
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/directx9_c/sixty_four_bit_programming_for_Game_Developers.asp
- John Paul Mueller. 24 Considerations for Moving Your Application to a 64-bit
Platform.
http://developer.amd.com/articlex.jsp?id=38
- Wikipedia: Static code analysis.
http://en.wikipedia.org/wiki/Static_code_analysis
- Sergei Sokolov. Bulletproofing C++ Code.
http://www.ddj.com/dept/debug/196802351
- Walter W. Schilling, Jr. and Mansoor Alam. Integrate Static Analysis Into
a Software Development Process.
http://www.ddj.com/dept/debug/193500967?cid=RSSfeed_DDJ_debugging
- Evgeniy Ryzhkov. Viva64: what is it and for whom is it meant?
http://www.viva64.com/articles/Viva64_-_what_is_and_for.html
Коментарии к статьеНет комментариев
|