64 бита

Содержание раздела

Заголовок Описание
Консольные приложения Windows (C++) Содержит сведения о консольных приложениях. Консольное приложение Win32 (или Win64) не имеет собственного окна и цикла обработки сообщений. Оно выполняется в окне консоли, а ввод и вывод обрабатываются через командную строку.
Пошаговое руководство. Создание классических приложений Windows (C++) создание простого Windows классического приложения.
Создание пустого классического приложения Windows создание проекта Windows desktop без файлов по умолчанию.
Добавление файлов в пустые приложения Win32 Добавление файлов в пустой проект.
Работа с файлами ресурсов Добавление изображений, значков, таблиц строк и других ресурсов в классическое приложение.
Ресурсы для создания игры с использованием DirectX (C++) Ссылки на материалы по созданию игр на C++.
Пошаговое руководство: Создание и использование статической библиотеки Создание двоичного файла с расширением LIB.
как использовать Windows SDK в Windows приложении для настольных компьютеров содержит шаги по настройке проекта для построения с помощью Windows SDK.

Разработка 64-битных приложений

Уроки разработки 64-битных приложений на языке Си/Си++Что такое 64-битные системыПоддержка 32-битных приложенийПеренос кода на 64-битные системы. За и противСоздание 64-битной конфигурацииСборка 64-битного приложенияОшибки в 64-битном кодеПроблемы выявления 64-битных ошибокСтатический анализ для выявления 64-битных ошибокПаттерн 01. Магические числаПаттерн 02. Функции с переменным количеством аргументовПаттерн 03. Операции сдвигаПаттерн 04. Виртуальные функцииПаттерн 05. Адресная арифметикаПаттерн 06. Изменение типа массиваПаттерн 07. Упаковка указателейПаттерн 08. Memsize-типы в объединенияхПаттерн 09. Смешанная арифметикаПаттерн 10. Хранение в double целочисленных значенийПаттерн 11. Сериализация и обмен даннымиПаттерн 12. ИсключенияПаттерн 13. Выравнивание данныхПаттерн 14. Перегруженные функцииПаттерн 15. Рост размеров структурФантомные ошибкиПрактическое знакомство с паттернами 64-битных ошибокОптимизация 64-битных программОсобенности создания инсталляторов для 64-битного окруженияОценка стоимости процесса 64-битной миграции Си/Си++ приложенийраздел с обзорами статей

Использование отладочных регистров

Однако я рано радовался. Отладчик-то заработал и если стек оставался кратен 8, работал, как положено, но регистры DR0-DR7 он изменять не мог, и они по-прежнему оставались нулевыми. А ведь это у нас один из основных механизмов поиска ошибок. Например, моя программа ведет себя ненормально. Я исследую ее переменные и вижу, что в переменной, скажем, X1 вдруг находится неверное значение, скажем, 5. Откуда оно там взялось? Я заново запускаю программу и задаю директиву встроенному отладчику:

K .X1 W1 =5

Что означает поставить аппаратную контрольную точку по записи байта в переменную X1. Если эта точка срабатывает, уже сам отладчик проверяет, стало ли равно значение 5. Если нет – отладчик возвращается обратно в программу, не снимая этой аппаратной контрольной точки. Когда значение становится равным 5, отладчик останавливается, не возвращаясь в программу. Поэтому внешне для меня программа запускается и останавливается в отладчике, когда X1 стала равна 5, показывая в программе точку записи в заданную переменную заданного значения (на самом деле начало следующей команды). И я сразу вижу, кто посмел испортить переменную X1 значением 5.

Для этого мощного и универсального способа поиска ошибок и нужны отладочные регистры. В Win32 все работало нормально. Что же в Win64 опять не так? И здесь причина в «новой» структурной обработке исключений. Поскольку ядро больше не вызывает UnhandledExceptionFilter, все сводится к работе диспетчера исключений и в случае успеха к вызову RtlRestoreContext. Легко убедится, что эта подпрограмма восстанавливает большинство регистров, но не DR0-DR7. На уровне привилегий пользователя она просто не может этого сделать.

Пришлось внутри отладчика внести еще одну доработку. В Win64 перед возвратом в диспетчер исключений отладчику с помощью обращения к SetThreadContext нужно отдельно установить требуемые значения DR0-DR7 для текущего потока. В общем случае устанавливать контекст для текущего потока, конечно, нельзя. Но это как раз тот частный случай, когда устанавливаются «безвредные» регистры. Для этого флаг контекста задается равным 100010H, т.е. требуется изменить только регистры DR0-DR7, не трогая остальных. Остальные изменит последующее выполнение RtlRestoreContext.

При этой доработке я, подобно многим другим бедолагам, наступил на все грабли и, конечно же, получил пресловутое сообщение об ошибке №998. И как многие, я тоже сначала воспринял его как какое-то нарушение прав доступа. Хотя оно сообщает всего-навсего о том, что адрес контекста при вызове SetThreadContext был не кратен 16, а нужно обязательно кратно, иначе и выдается ошибка 998.

Терминология

  • приложение Win32 — это Windows классическое приложение на языке C++, которое может использовать Windows собственные api -интерфейсы C и (или ) api COM и api библиотеки стандартных библиотек, а также сторонние библиотеки. приложение Win32, выполняемое в окне, требует, чтобы разработчик работал явно с Windows сообщениями внутри функции Windows процедуры. Несмотря на имя, приложение Win32 можно скомпилировать 32 как 64-разрядный (x86) или 64-разрядный (x64) двоичный файл. в Visual Studio IDE термины x86 и Win32 являются синонимами.

  • Модель COM — это спецификация, которая позволяет программам, написанным на разных языках, взаимодействовать друг с другом. многие компоненты Windows реализуются как com-объекты и следуют стандартным правилам COM для создания объектов, обнаружения интерфейсов и уничтожения объектов. Использование объектов COM из классических приложений C++ относительно просто, но написание собственного COM-объекта является более сложным. Библиотека активных шаблонов (ATL) предоставляет макросы и вспомогательные функции, УПРОЩАЮЩИЕ разработку com.

  • приложение MFC — это Windows классическое приложение, которое использует Microsoft Foundation Classes для создания пользовательского интерфейса. Приложение MFC также может использовать компоненты COM, а также API CRT и библиотеки стандартных библиотек. MFC предоставляет объектно-ориентированную оболочку с тонким C++ для циклов оконных сообщений и Windows api. MFC является выбором по умолчанию для приложений (особенно приложений корпоративного типа), которые имеют множество элементов управления пользовательского интерфейса или пользовательских элементов управления. MFC предоставляет удобные вспомогательные классы для управления окнами, сериализации, обработки текста, печати и современных элементов пользовательского интерфейса, таких как лента. Для эффективной работы с MFC вы должны быть знакомы с Win32.

  • Приложение или компонент C++/CLI использует расширения для синтаксиса C++ (как это разрешено стандартом C++), чтобы обеспечить взаимодействие между .NET и машинным кодом C + +. приложение C++/cli может содержать части, работающие в собственном коде, и части, которые выполняются в платформа .NET Framework с доступом к библиотеке базовых классов .net. C++/CLI является предпочтительным вариантом при наличии машинного кода C++, который должен работать с кодом, написанным на C# или Visual Basic. Он предназначен для использования в библиотеках DLL .NET, а не в коде пользовательского интерфейса. Дополнительные сведения см. в статье Программирование .NET с использованием C++/CLI (Visual C++).

любое классическое приложение в C++ может использовать среду выполнения C (CRT), классы и функции стандартной библиотеки, COM-объекты и открытые функции Windows, которые вместе называются Windows API. общие сведения о Windows классических приложениях на c++ см. в разделе Начало работы с Win32 и C++.

Разбор: что значат этот весь текст?

На 1 строчке: «format PE Console» — это строчка говорит FASM-у какой файл скомпилировать, точнее 1 слово, все остальные слова это аргументы (можно так сказать).

PE — EXE файл, программа.

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

Но есть кроме это остальные:

  • format MZ — EXE-файл НО под MS-DOS

  • format PE — EXE-файл под Windows, аналогично format PE GUI 4.0

  • format PE64 — EXE-файл под Windows, 64 битное приложение.

  • format PE GUI 4.0 — EXE-файл под Windows, графическое приложение.

  • format PE Console — EXE-файл под Windows, консольная программа. (просто подключается заранее консоль)

  • format PE Native — драйвер

  • format PE DLL — DLL-файл Windows, поясню позднее.

  • format COFF — OBJ-файл Linux

  • format MS COFF — аналогично предыдущему

  • format ELF — OBJ-файл для gcc (Linux)

  • format ELF64 — OBJ-файл для gcc (Linux), 64-bit

Сразу за командой (для компилятора) идет это значит комментарий. К сожалению он есть только однострочный.

3 строка:

Говорим windows-у где\в каком месте стартовать. «start» это метка, но о метках чуть позже.

5 строка:

Подключает к проекту файл, в данном случае «win32a.inc» он находиться в папке INCLUDE (в папке с FASM). этот файл создает константы и создает макросы для облегчения программирования.

8 строка:

Секция данных, то есть программа делиться на секции (части), к этим секциям мы можем дать разрешение, имя.

Флаг «data» (Флаг это бит\байт\аргумент хранившей в себе какую-то информацию) говорит то что эта секция данных.

Флаги «readable writeable» говорят то что эта секция может читаться кем-то и записываться кем-то.

Текст ‘.data’ — имя секции

10 строка:

hello — это метка, она может быть любого имени (почти, есть некоторые зарезервированные имена), эта метка хранит в себе адрес строки, это не переменная, а просто адрес, но чтобы не запоминать адреса в ручную, помогает FASM он запоминает адрес и потом когда видит эту метку снова, то он заменяет слово на адрес.

db — говорит то что под каждый символ резервируем 1 байт. То есть 1 символ храниться в одном байте.

‘hello world!’ — наша строка в кодировке ASCII

Что значит «,0» в конце строки? — это символ с номером 0 (или просто ноль), у вас на клавиатуре нет клавиши которая имела символ с номером 0, по этому этот символ используют как показатель конца строки. То есть это значит конец строки. Просто ноль записываем в байт после строки.

12 строка:

Флаг «code» — говорит то что это секция кода.

Флаг «executable» — говорит то что эта секция исполняема, то есть в этой секции может выполняться код.

Все остальное уже разобрали.

14 строка:

Это второй вид меток. Просто эта метка указывает на следующую команду

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

15 строка:

Функция printf — выводит текст\число в консоль. В данном случае текст по адресу «hello»

Это штото на подобие команды, но это и близко не команда ассемблера, а просто макрос.

Макрос — Это макро команда для компилятора, то есть вместо имени макроса подставляется что-то другое.

Например, макро команда invoke делиться на такие команды: (взят в пример команда с 15 строки)

Не переживайте если нечего не поняли.

17 строка:

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

20 строка:

ExitProcess — WinAPI функция, она завершает программу. Она принимает значение, с которым завершиться, то есть код ошибки, ноль это нет ошибок.

23 строка:

Флаг «import» — говорит то что это секция импорта библиотек.

24-25 строки:

Макро команда «library» загружает DLL библиотеки в виртуальную память (не в ОЗУ, вам ОЗУ не хватит чтоб хранить всю виртуальную память).

Что такое DLL объясню позже.

kernel — имя которое привязывается к библиотеке, оно может быть любым.

Следующий текст после запятой: — это имя DLL библиотеки который вы хотите подключить.

Дальше есть знак это значит что текст на следующей строке нужно подставить в эту строку.

То есть код:

Заменяется на:

Это нужно потому что у ассемблера 1 строка это 1 команда.

27-28 строка:

— Макро команда, которая загружает функции из DLL.

— Имя к которой привязана DLL, может быть любым.

— Как будет называться функция в программе, это имя будет только в вашей программе, и по этому имени вы будете вызывать функцию. (WinAPI функция)

— Это имя функции которое будет загружено из DLL, то есть это имя функции которое прописано в DLL.

Дальше думаю не стоит объяснять, вроде все понятно.

Идея и реализация структурной обработки исключений

Но позвольте, как же тогда в Win64 вообще вызывается обработчик, установленный по SetUnhandledExceptionFilter? Именно здесь и вступает в игру структурная обработка исключений.

Сама идея структурной обработки исключений, заключающаяся в попытке выдать информацию об исключении и обработать исключение на возможно более высоком уровне, весьма здрава. В самом деле, ведь Windows имеет адрес команды, где случилось исключение. Если заранее сообщить ей какая подпрограмма занимает диапазон адресов, в который попало место исключения, и как в этом случае обрабатывать исключения, то можно автоматически вызывать именно нужный в данный момент обработчик, а не какой-то универсальный и всеобъемлющий, но безликий и неинформативный.

Вот только реализация этой идеи в Win64 вызывает массу нареканий.

Причем первый шаг обработки в Win64 вполне нормален. Диспетчер по адресу прерванной команды пытается «узнать» подпрограмму с помощью вызова RtlLookupFunctionEntry. Если информация о подпрограмме имеется во внутренней таблице (а зарегистрировать ее можно, указав прямо внутри заголовка EXE-файла или обратившись к RtlAddFunctionTable), то дело в шляпе, становится известен обработчик исключений именно для данной подпрограммы и диспетчер вызывает его. Но вот, если по адресу исключения подпрограмма не опознана…

Здесь начинаются совсем неочевидные вещи. Не найдя в какой из известных ему подпрограмм произошло исключение, диспетчер не сдается и начинает анализировать текущий стек программы дальше. В Microsoft назвали это «раскруткой стека».

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

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

Во-вторых, при этом начисто забыли о пошаговом режиме отладки. Например, при вызове какой-нибудь процедуры API Windows подготовка к вызову начинается с команды типа sub rsp,20h. При выполнении этой команды в режиме трассировки, т.е. при очередном пошаговом исключении в вершине стека временно оказываются четыре совершенно случайных значения, не имеющих прямого отношения к программе.

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

Несмотря на это, анализ стека, проводимый в диспетчере исключений, включает даже чтение кода команды (в предположении, что очередные 8 байт – это адрес возврата), «дизассемблирование» этого кода и моделирование (!) работы этой команды с точки зрения изменения стека. Да-да, большую часть диспетчера исключений занимает дизассемблер (как и в моем интерактивном отладчике). Кстати, замечу, что постепенно дизассемблирование превращается в кошмар – по моим подсчетам в современном процессоре x86 более 670 разных команд, да еще со всевозможными «префиксами».

Особенности обработки исключения

Вернемся к рассматриваемому случаю интерактивного отладчика. Поскольку сначала я не знал о структурной обработке исключений и принудительном анализе стека в Win64, я, конечно, не регистрировал никаких «внутрипоточных» обработчиков, интуитивно считая, что уже установленный UnhandledExceptionFilter будет вызван в любом случае (как в Win32). В результате этого непонимания отладчик начинал работу пошаговой отладки со случайным содержимым стека. Впрочем, один адрес в самой глубине стека был не случайным. Это был адрес возврата, установленный в результате работы подпрограммы RtlUserThreadStart, которая собственно и запускала мою программу. Именно эта подпрограмма оказалась зарегистрирована в Windows и именно ее обработчик все-таки вызывал UnhandledExceptionFilter, и в конце-концов мой встроенных отладчик.

Таким образом, вся работа отладчика, переделанного для Win64, висела на тоненькой ниточке процесса исследования стека. Если диспетчер при анализе стека доходил до адреса, входящего в RtlUserThreadStart, очередное обращение RtlLookupFunctionEntry давало успех и на данном шаге мой отладчик вызывался.

Но стоило, например, в трассировке выполнить команду dec rsp, как диспетчер немедленно прекращал поиск в стеке адресов возврата (считая, что они должны лежать исключительно по адресам, кратным 8) и я вместо очередного шага получал на экране стандартное сообщение от системы об исключении именно пошагового режима (что сделало бы честь самому Капитану Очевидность).

Кроме не кратности указателя стека 8, много крови мне испортил вот этот фрагмент диспетчера исключений (напомню, сам диспетчер находится в NTDLL.DLL):

Здесь достается очередное значение из стека (моделируемое значение указателя стека находится в R8) и сравнивается с предыдущим анализируемым значением (оно находится в R12). Причем в это место диспетчера управление попадает, если для очередного значения стека вызов RtlLookupFunctionEntry ничего не нашел. Т.е. если для очередного значения в стеке ничего не найдено, а следующее значение – точно такое же, диспетчер прекращает исследование стека вообще. Смысл этой проверки для меня непонятен. В моем случае два одинаковых значения – это были просто два нуля, т.е. уж точно не адреса возврата, тем не менее, два подряд любых одинаковых значения в стеке являются для диспетчера сигналом, что он заблудился, и анализ стека надо прекратить. Тем самым, объяснилось странное поведение программы при записи двух одинаковых значений в стек подряд.

Но все же самым неприятным для меня оказался другой фрагмент диспетчера. Вот этот:

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

Но вот дополнительная проверка указателя стека на кратность 8 – это безобразие. Какое собственно Windows дело кратен или не кратен указатель стека 8 в программе, раз обработчик все равно уже найден и стек дальше исследовать не надо? Пусть обработчик сам и разбирается с кратностью указателя стека. Из-за этой маленькой и вредной команды test bpl,7 мне пришлось сильно переделывать все свои библиотеки.

Обидно, что сам процессор и программы нормально работают с любым указателем стека. И только при отладке встроенным отладчиком невозможно пошагово пройти команды, делающие (хотя бы даже временно) стек не кратным 8. В Win32 я привык свободно обращаться с указателем стека. Конечно, есть доводы насчет эффективности выполнения по кратным адресам. Но вот простой контрпример: в стеке лежит текстовая строка, от которой нужно отрезать слева число байт, записанное, скажем, в EAX. В Win32 я мог делать это единственной командой:

А теперь нужно еще двигать всю строку в стеке, что бы ее новое начало легло по адресу, кратному 8. Причем в общем случае может потребоваться и сдвиг обрезанной строки вправо и сдвиг влево. Разве это эффективно? И все лишь для того, чтобы встроенный отладчик вызывался при пошаговом режиме! Справедливости ради, следует отметить, что подключаемый отладчик не имеет таких проблем (и WinDbg тому доказательство). Но мне нужен не подключаемый, а собственный встроенный отладчик!

Функции CRT для управления потоками

Все программы Win32 имеют по крайней мере один поток. Любой поток может создавать дополнительные потоки. Поток может выполнять свою работу быстро, а затем прерываться или оставаться активным в течение всего жизненного цикла программы.

Библиотеки CRT предоставляют следующие функции для создания и завершения потоков: _beginthread, _beginthreadex, _endthread и _endthreadex.

Функции и создают новый поток и возвращают идентификатор потока, если операция выполнена успешно. Поток завершается автоматически при завершении выполнения. Или может завершиться с помощью вызова метода или .

Примечание

При вызове подпрограмм среды выполнения C из программы, созданной с помощью LIBCMT. lib, необходимо запускать потоки с помощью функции или . Не используйте функции Win32 и . Использование может привести к взаимоблокировке, когда более чем один поток блокируется, ожидая, пока приостановленный поток завершит свой доступ к структуре данных времени выполнения C.

Функции _beginthread и _beginthreadex

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

функции и похожи на функцию CREATETHREAD в API Win32, но имеют следующие отличия:

Они инициализируют определенные переменные библиотеки времени выполнения языка C

Это важно только в том случае, если вы используете библиотеку времени выполнения C в своих потоках.

обеспечивает управление атрибутами безопасности. Эту функцию можно использовать для запуска потока в приостановленном состоянии.

и возвращают маркер в новый поток, если он был успешным, или код ошибки, если произошла ошибка.

Функции _endthread и _endthreadex

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

Преимущества 64-разрядной Windows

64-разрядная операционная система поддерживает гораздо больше физической памяти, чем 32-разрядная операционная система. Например, большинство 32-разрядных систем Windows поддерживают не более 4 гигабайт физической памяти с 3 гигабайтами адресного пространства для каждого процесса, а 64-разрядная Windows поддерживает до 2 терабайт физической памяти с 8 терабайтами адресного пространства для каждого процесса. Увеличенная физическая память включает следующие преимущества для приложений:

  • Каждое приложение может поддерживать больше пользователей. Все или часть каждого приложения должны быть реплицированы для каждого пользователя, для которого требуется дополнительная память.
  • Каждое приложение имеет лучшую производительность. Увеличение физической памяти позволяет выполнять больше приложений одновременно и оставаться полностью резидентными в основной памяти системы. Это снижает или устраняет производительность переключения страниц на диск и с диска.
  • Каждое приложение имеет больше памяти для хранения и обработки данных. Базы данных могут хранить больше данных в физической памяти системы. Доступ к данным выполняется быстрее, так как чтение дисков не требуется.
  • Приложения могут легко и надежно управлять большими объемами данных. По этой причине для работы с видеоматериалами требуется 64-разрядная Windows. Моделирование для научных и финансовых приложений значительно отличается от структур данных, находящихся в памяти, которые невозможно использовать в 32-разрядной Windows.

Существуют также важные преимущества для предприятий:

  • Повышенная производительность. Работники знаний могут тратить свое время на мышление и производство, а не ждать, пока программное обеспечение завершит свои задачи.
  • Более низкая стоимость владения. Каждый сервер может поддерживать большее количество пользователей и приложений, поэтому для вашего бизнеса потребуется меньше серверов. Это приводит непосредственно к меньшим затратам на управление — одной из самых высоких затрат в любой вычислительной среде.
  • Новые возможности приложений. Новые приложения можно разрабатывать без барьеров, накладываемых 32-разрядными Windows. Новые графические приложения упрощают работу и делают его более приятными. Задачи с большим объемом данных, которые сегодня невозможно выполнить с помощью 64-разрядной Windows.

Ставим MinGW

Авторы многих дистрибутивов GNU/Linux уже постарались за нас, так что многие кросс-версии GCC, включая MinGW, можно поставить из репозиториев.

Например, в Fedora:

Или в Debian:

Если ты используешь macOS, то MinGW можно поставить из Homebrew: .

INFO

MinGW-w64, несмотря на название, поддерживает и Win32, и Win64. Это форк MinGW, который создали в первую очередь для реализации недостающей в оригинальном проекте поддержки Win64, отсюда и название.

В Fedora также присутствует ряд готовых кросс-версий популярных библиотек, например .

Hello World

Для демонстрации соберем традиционный hello world. У кросс-версий GCC всегда есть префикс, для MinGW используются префиксы и .

Тестирование кросс-компилированных программ для других архитектур — непростая задача, но, поскольку наша целевая платформа — Windows на x86, мы легко можем протестировать их в Wine:

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку!
Подробнее

Вариант 2. Открой один материал

Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя!
Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.

Я уже участник «Xakep.ru»

6.5. Ошибки использования 32-битных переменных в качестве индексов

const size_t size = ...;
char *array = ...;
char *end = array + size;
for (unsigned i = 0; i != size; ++i)
{
  const int one = 1;
  end = 0;
}

Выражение «-i» имеет тип unsigned и имеет значение 0x00000000u.
Переменная ‘one’ будет расширена от типа ‘int’ до типа unsigned и будет равна 0x00000001u. Примечание: Тип int расширяется (согласно стандарту языка Си++) до типа ‘unsigned’, если он участвует в операции, где второй аргумент имеет тип unsigned.
Происходи операция вычитания, в котором участвуют два значения типа unsigned и результат выполнения операции равен 0x00000000u — 0x00000001u = 0xFFFFFFFFu

Обратите внимание, что результат имеет беззнаковый тип.
На 32-битной системе обращение к массиву по индексу 0xFFFFFFFFu эквивалентно использованию индекса -1. То есть end является аналогом end

В результате происходит корректная обработка элемента массива.

Прикладное программирование и 64-битные системы

64-битные

  1. IA-64 64-битная микропроцессорная архитектура, разработанная совместно компаниями Intel и Hewlett Packard. Реализована в микропроцессорах Itanium и Itanium 2. Для более подробного знакомства с архитектурой IA-64 можно обратиться к следующим статьям в Wikipedia: «IA-64», «Itanium», «Itanium 2». Архитектуру Itanium поддерживает большое количество производителей серверов: Bull, Fujitsu, Fujitsu Siemens Computers, Hitachi, HP, NEC, SGI и Unisys. Эти производители присоединились к Intel и множеству разработчиков ПО для создания Itanium Solutions Alliance, с целью продвижения архитектуры и ускорения темпов портирования ПО.
  2. Intel 64 (AMD64x86-64 / x64 / EM64T) — данная архитектура представляет собой расширение архитектуры x86 с полной обратной совместимостью. Существует множество вариантов названия данной архитектуры, что приводит к путанице, хотя, по сути, все эти названия обозначают одно и тоже: x86-64, AA-64, Hammer Architecture, AMD64, Yamhill Technology, EM64T, IA-32e, Intel 64, x64. Более подробно узнать о том, как появилось так много названий, можно в статье из Wikipedia: «X86-64». Процессоры с архитектурой Intel 64 нашли широкое распространение персональных компьютерах. И скорее всего ваш компьютер оснащен именно процессором с данной архитектурой.

Win64

Макросы для условных объявлений

Некоторые функции, зависящие от конкретной версии Windows, объявляются с помощью условного кода. Это позволяет использовать компилятор для определения того, использует ли приложение функции, которые не поддерживаются в целевых версиях Windows. Чтобы скомпилировать приложение, использующее эти функции, необходимо определить соответствующие макросы. В противном случае появится сообщение об ошибке C2065.

Файлы заголовков Windows используют макросы, чтобы указать, какие версии Windows поддерживают многие элементы программирования. Поэтому необходимо определить эти макросы, чтобы использовать новые функциональные возможности, представленные в каждом выпуске основной операционной системы. (Отдельные файлы заголовков могут использовать различные макросы, поэтому, если возникают проблемы компиляции, проверьте файл заголовка, содержащий определение для условных определений.) Дополнительные сведения см. в разделе SdkDdkVer.h.

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

Минимальная требуемая система Значение для NTDDI_VERSION
Windows 10 1903 «19H1» NTDDI_WIN10_19H1 (0x0A000007)
Windows 10 1809 «Redstone 5» NTDDI_WIN10_RS5 (0x0A000006)
Windows 10 1803 «Redstone 4» NTDDI_WIN10_RS4 (0x0A000005)
Windows 10 1709 «Redstone 3» NTDDI_WIN10_RS3 (0x0A000004)
Windows 10 1703 «Redstone 2» NTDDI_WIN10_RS2 (0x0A000003)
Windows 10 1607 «Redstone 1» NTDDI_WIN10_RS1 (0x0A000002)
Windows 10 1511 «Пороговое значение 2» NTDDI_WIN10_TH2 (0x0A000001)
Windows 10 1507 «Пороговое значение» NTDDI_WIN10 (0x0A000000)
Windows 8.1 NTDDI_WINBLUE (0x06030000)
Windows 8 NTDDI_WIN8 (0x06020000)
Windows 7 NTDDI_WIN7 (0x06010000)
Windows Server 2008 NTDDI_WS08 (0x06000100)
Windows Vista с пакетом обновления 1 (SP1) NTDDI_VISTASP1 (0x06000100)
Windows Vista NTDDI_VISTA (0x06000000)
Windows Server 2003 с пакетом обновления 2 (SP2) NTDDI_WS03SP2 (0x05020200)
Windows Server 2003 с пакетом обновления 1 (SP1) NTDDI_WS03SP1 (0x05020100)
Windows Server 2003 NTDDI_WS03 (0x05020000)
Windows XP с пакетом обновления 3 (SP3) NTDDI_WINXPSP3 (0x05010300)
Windows XP с пакетом обновления 2 (SP2) NTDDI_WINXPSP2 (0x05010200)
Windows XP с пакетом обновления 1 (SP1) NTDDI_WINXPSP1 (0x05010100)
Windows XP NTDDI_WINXP (0x05010000)

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

Минимальная требуемая система Минимальное значение для _WIN32_WINNT и WINVER
Windows 10 _WIN32_WINNT_WIN10 (0x0A00)
Windows 8.1 _WIN32_WINNT_WINBLUE (0x0603)
Windows 8 _WIN32_WINNT_WIN8 (0x0602)
Windows 7 _WIN32_WINNT_WIN7 (0x0601)
Windows Server 2008 _WIN32_WINNT_WS08 (0x0600)
Windows Vista _WIN32_WINNT_VISTA (0x0600)
Windows Server 2003 с пакетом обновления 1 (SP1) Windows XP с пакетом обновления 2 (SP2) _WIN32_WINNT_WS03 (0x0502)
Windows Server 2003, Windows XP _WIN32_WINNT_WINXP (0x0501)
Минимальная требуемая версия Минимальное значение _WIN32_IE
Internet Explorer 11.0 _WIN32_IE_IE110 (0x0A00)
Internet Explorer 10.0 _WIN32_IE_IE100 (0x0A00)
Internet Explorer 9.0 _WIN32_IE_IE90 (0x0900)
Internet Explorer 8,0 _WIN32_IE_IE80 (0x0800)
Internet Explorer 7.0 _WIN32_IE_IE70 (0x0700)
Internet Explorer 6.0 с пакетом обновления 2 (SP2) _WIN32_IE_IE60SP2 (0x0603)
Internet Explorer 6.0 с пакетом обновления 1 (SP1) _WIN32_IE_IE60SP1 (0x0601)
Internet Explorer 6.0 _WIN32_IE_IE60 (0x0600)
Internet Explorer 5,5 _WIN32_IE_IE55 (0x0550)
Internet Explorer 5.01 _WIN32_IE_IE501 (0x0501)
Internet Explorer 5.0, 5.0a, 5.0b _WIN32_IE_IE50 (0x0500)

Планирование

  • Определите величину усилий, необходимых для порта. Определите объем работы, определив следующие элементы:

    • Проблема 32-разрядного кода. Скомпилируйте 32-разрядный код с помощью 64-разрядного компилятора и изучите степень ошибок и предупреждений.
    • Общие компоненты или зависимости. Определите, какие компоненты в приложении происходят из других команд и планирует ли эти команды разрабатывать 64-разрядные версии кода.
    • Устаревший код или код сборки. 16-разрядные приложения на основе Windows не работают на 64-разрядной Windows и должны быть перезаписаны. Хотя код сборки x86 выполняется в WOW64, может потребоваться переписать этот код, чтобы воспользоваться скоростью архитектуры Intel Itanium.
  • Переносите все приложение, а не только части приложения.

    Хотя можно перенести фрагменты приложения или ограничить код до 2G с /LARGEADDRESSAWARE:NO, эта стратегия торгуется краткосрочной выгодой для долгосрочной боли.

    Примечание

    /LARGEADDRESSAWARE:NO игнорируется для двоичного файла ARM64.

  • Найдите замену технологий, которые не будут перенесены.

    Некоторые технологии, включая DAO (объект доступа к данным) и ядро СУБД Jet Red, не будут перенесены в 64-разрядные Windows.

  • Рассматривайте 64-разрядную версию как отдельный выпуск продукта.

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

Рейтинг
( Пока оценок нет )
Editor
Editor/ автор статьи

Давно интересуюсь темой. Мне нравится писать о том, в чём разбираюсь.

Понравилась статья? Поделиться с друзьями:
Работатека
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: