Общие сведения о соглашениях ABI ARM32

Двоичный интерфейс приложения (ABI) для кода, скомпилированного для Windows на процессорах ARM, основан на стандартном встроенном двоичном интерфейсе приложения ARM. В этой статье указаны различия между системой Windows на устройствах ARM и стандартной системой. В этом документе рассматривается ABI ARM32. Дополнительные сведения об интерфейсе ABI ARM64 см. в статье Общие сведения о соглашениях ABI ARM64. Дополнительные сведения о стандартном встроенном двоичном интерфейсе приложения ARM см. в статье Двоичный интерфейс приложения для архитектуры ARM (внешняя ссылка).

Базовые требования

Система Windows на ARM всегда предполагает, что она выполняется в архитектуре ARMv7. В оборудовании должна быть реализована поддержка вычислений с плавающей запятой в форме VFPv3-D32 или более поздней версии. Технология VFP должна поддерживать форматы с плавающей запятой одиночной и двойной точности на оборудовании. Среда выполнения Windows не поддерживает эмуляцию вычислений с плавающей запятой для выполнения на оборудовании без VFP.

В оборудовании также должна быть реализована поддержка усовершенствованных расширений SIMD (NEON), куда входят как целочисленные операции, так и операции с плавающей запятой. Поддержка эмуляции во время выполнения отсутствует.

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

Порядок байтов

Windows на ARM выполняется в режиме с прямым порядком байтов. Как компилятор MSVC, так и среда выполнения Windows всегда ожидают данные с прямым порядком байтов. Инструкция SETEND в архитектуре набора инструкций (ISA) ARM позволяет даже коду пользовательского режима изменять текущий порядок байтов. Но делать это не рекомендуется, так как это опасно для приложения. Если исключение создано в режиме с обратным порядком, поведение непредсказуемо. Это может привести к сбою приложения в пользовательском режиме или проверке наличия ошибок в режиме ядра.

Точное понимание

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

  • Загрузки и сохранения целого числа размером в полслова (16 бит) и полное слово (32 бита) выравнивать не требуется. Оборудование само эффективно и легко обрабатывает их.

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

  • Загрузку и сохранение двойных (LDRD/STRD) и множественных (LDM/STM) операций следует выравнивать. Ядро прозрачно обрабатывает большинство из них, однако на это расходуются значительные ресурсы.

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

Набор инструкций

Набор инструкций для Windows на ARM ограничен исключительно режимом Thumb-2. Весь код, выполняемый на этой платформе, должен запускаться и постоянно оставаться в режиме Thumb. Попытка переключиться на устаревший набор инструкций ARM может быть успешна. Однако в этом случае любые происходящие исключения или прерывания могут привести к сбою приложения в пользовательском режиме или к отладке в режиме ядра.

Побочный эффект этого требования заключается в том, что все указатели кода должны иметь заданный младший бит. Затем, когда выполнены загрузка и разветвление с помощью BLX или BX, процессор остается в режиме Thumb. Он не пытается выполнить целевой код как 32-разрядные инструкции ARM.

Инструкции SDIV/UDIV

Использование инструкций деления целых чисел SDIV и UDIV полностью поддерживается даже на платформах, не имеющих собственного оборудования для их обработки. Дополнительные затраты при каждом разделении SDIV или UDIV на процессоре Cortex-A9 составляют примерно 80 циклов. Это значение добавляется к общему времени разделения, равному 20–250 циклам, в зависимости от входных данных.

Целочисленные регистры

Процессор ARM поддерживает 16 целочисленных регистров.

Зарегистрировать Переменный? Роль
r0 Переменный Параметр, результат, оперативный регистр 1
r1 Переменный Параметр, результат, оперативный регистр 2
r2 Переменный Параметр, оперативный регистр 3
r3 Переменный Параметр, оперативный регистр 4
r4 Неизменяемый
r5 Неизменяемый
r6 Неизменяемый
r7 Неизменяемый
r8 Неизменяемый
r9 Неизменяемый
r10 Неизменяемый
r11 Неизменяемый Указатель фрейма
r12 Переменный Оперативный регистр для вызова внутри процедуры
r13 (SP) Неизменяемый Указатель стека
r14 (LR) Неизменяемый Регистр связи
r15 (PC) Неизменяемый Счетчик команд

Сведения об использовании регистров параметра и возвращаемого значения см. в разделе "Передача параметров" этой статьи.

Windows использует r11 для быстрого прохода кадра стека. Дополнительные сведения см. в разделе "Проверка стека". Из-за этого требования, r11 всегда должен указывать на самое верхнее звено в цепочке. Не используйте r11 для выполнения общих задач: ваш код не создаст правильные проверки стека во время анализа.

Регистры VFP

Windows поддерживает только варианты ARM, имеющие поддержку сопроцессора VFPv3-D32. Это означает, что регистры с плавающей запятой всегда существуют и на них можно полагаться при передаче параметров. Также, полный набор из 32 регистров доступен для использования. Регистры VFP и их использование описаны в следующей таблице.

Одиночные Двойные Счетверенные Переменный? Роль
s0—s3 d0—d1 q0 Переменный Параметры, результат, оперативный регистр
s4—s7 d2—d3 q1 Переменный Параметры, оперативный регистр
s8—s11 d4—d5 q2 Переменный Параметры, оперативный регистр
s12—s15 d6—d7 q3 Переменный Параметры, оперативный регистр
s16—s19 d8—d9 q4 Неизменяемый
s20—s23 d10—d11 q5 Неизменяемый
s24—s27 d12—d13 q6 Неизменяемый
s28—s31 d14—d15 q7 Неизменяемый
d16—d31 q8—q15 Переменный

В следующей таблице представлены битовые поля регистра управления и состояний для операций с плавающей точкой (FPSCR).

Bits Значение Переменный? Роль
31—28 NZCV Переменный Флаги состояния
27 QC Переменный Совокупное насыщение
26 AHP Неизменяемый Альтернативное управление с половинной точностью
25 DN Неизменяемый Управление режимом по умолчанию NaN
24 FZ Неизменяемый Управление режимом обнуления
23—22 RMode Неизменяемый Управление режимом округления
21—20 Stride Неизменяемый Шаг вектора, должен всегда быть равен 0.
18—16 Len Неизменяемый Длина вектора, должен всегда быть равен 0.
15, 12—8 IDE, IXE и т. д. Неизменяемый Биты отслеживания исключения, должен всегда быть равен 0.
7, 4—0 IDC, IXC и т. д. Переменный Совокупные флаги исключения

Исключения для плавающей запятой

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

Передача параметров

Windows на ABI ARM следует правилам ARM при передаче параметров для функций, не являющихся вариадическими. Правила ABI включают расширения VFP и Advanced SIMD. Эти правила соответствуют Стандарту вызова процедур для архитектуры ARM, в сочетании с расширениями VFP. По умолчанию в регистры передаются четыре первых целочисленных аргумента и до восьми аргументов с плавающей запятой или векторных аргументов. Все последующие аргументы передаются в стек. Аргументы назначаются регистрам или стеку с помощью следующей процедуры.

Этап A. Инициализация

Инициализация выполняется только один раз перед началом обработки аргументов.

  1. Для номера следующего базового регистра (NCRN) устанавливается r0.

  2. Регистры VFP помечаются как невыделенные.

  3. Для адреса следующего помещенного в стек аргумента (NSAA) устанавливается текущий SP.

  4. Если вызывается функция, возвращающая результат в память, то адрес этого результата помещается в r0, а для NCRN устанавливается значение r1.

Этап B. Предварительное заполнение и расширение аргументов

Для каждого имеющегося аргумента применяется первое соответствующее правило из следующего списка.

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

  2. Если аргумент имеет тип байта или 16-битного полуслова, то он расширяется нулем или знаком до полного 32-битного слова и считается 4-байтным аргументом.

  3. Если аргумент имеет составной тип, его размер округляется до ближайшего значения, кратного 4.

Этап C. Назначение аргументов для регистров и стека

Для каждого имеющегося аргумента по очереди применяются следующие правила, пока аргумент не будет назначен.

  1. Если аргумент имеет тип VFP и доступно достаточно последовательных невыделенных регистров VFP нужного типа, то аргумент назначается последовательности таких регистров с наименьшими номерами.

  2. Если аргумент имеет тип VFP, все оставшиеся неназначенные регистры помечаются как недоступные. Адрес NSAA корректируется вверх до тех пор, пока не будет выровнен для данного типа аргумента, после чего аргумент копируется в стек по скорректированному NSAA. После этого адрес NSAA увеличивается на размер аргумента.

  3. Если аргумент требует 8-байтового выравнивания, номер NCRN округляется до следующего четного номера регистра.

  4. Если размер аргумента в 32-разрядных словах превышает разность значений r4 и NCRN, этот аргумент копируется в базовые регистры, начиная с номера NCRN, при этом менее важные биты занимают регистры с меньшими номерами. Номер NCRN увеличивается на число использованных регистров.

  5. Если номер NCRN меньше r4, а адрес NSAA равен SP, аргумент делится между базовыми регистрами и стеком. Первая часть аргумента копируется в базовые регистры, начиная с номера NCRN, и до r3 включительно. Оставшаяся часть аргумента копируется в стек, начиная с адреса NSAA. Номер NCRN задается равным r4, а адрес NSAA увеличивается на размер разности размера аргумента и числа переданных регистров.

  6. Если аргумент требует 8-байтового выравнивания, адрес NSAA округляется до следующего выровненного по 8 байтам адреса.

  7. Аргумент копируется в память по адресу NSAA. Адрес NSAA увеличивается на размер аргумента.

Регистры VFP не используются для функций с переменным числом аргументов, поэтому правила 1 и 2 этапа В игнорируются. Это означает, что функция с переменным числом аргументов может начинаться с необязательной передачи {r0–r3}, чтобы добавить аргументы регистров перед дополнительными аргументами, переданными вызывающим объектом, а затем получить доступ ко всему списку аргументов прямо из стека.

Целочисленные значения возвращаются в r0 и при необходимости расширяются до r1 для 64-разрядных значений. Значения с плавающей запятой VFP/NEON или типом SIMD возвращаются в s0, d0 или q0.

Стек

Стек должен всегда оставаться выровненным по 4 байтам, а также он должен быть выровненным по 8 байтам на любой границе функции. Это необходимо для поддержки частого использования заблокированных операций с 64-разрядными переменными стека. Стандартный встроенный двоичный интерфейс приложения ARM требует, чтобы стек был выровнен по 8 байтам в любом общедоступном интерфейсе. Для обеспечения согласованности двоичный интерфейс приложения Windows на ARM считает любую границу функции общедоступным интерфейсом.

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

Функции, выделяющие 4 КБ или больше в стеке, должны проверять, что каждая страница перед последней обрабатывается по порядку. Это гарантирует, что никакой код не обойдет страницы защиты, используемые Windows для расширения стека. Обычно расширение осуществляется с помощью вспомогательной функции __chkstk, которая получает все выделение стека в байтах, поделенное на 4, из r4 и возвращает итоговое значение выделение стека в байтах обратно в r4.

Красная зона

8-байтовая область сразу после текущего указателя стека зарезервирована для анализа и динамического исправления. Это позволяет вставить тщательно выверенный код, который хранит 2 регистра в [sp, #-8] и временно использует их для произвольных целей. Ядро Windows гарантирует, что эти 8 байт не будут перезаписаны при возникновении исключения или прерывания как в пользовательском режиме, так и в режиме ядра.

Стек ядра

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

Особенности C/C++

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

wchar_t определяется как эквивалент unsigned short для сохранения совместимости с другими платформами.

Проверка стека

Код Windows компилируется с включенными указателями фреймов (/Oy (подавление указателей фрейма)) для обеспечения быстрой проверки стека. Обычно регистр r11 указывает на следующее звено в цепочке, которое представляет собой пару {r11, lr}, задающую указатель на предыдущий фрейм в стеке и адрес возврата. Мы рекомендуем также включить указатели фреймов в вашем коде для улучшения профилирования и трассировки.

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

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

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

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

Счетчик циклов

Процессоры ARM под управлением Windows должны поддерживать счетчик циклов, однако прямое использование такого счетчика может вызвать проблемы. Чтобы избежать их, Windows на ARM использует неопределенный код операции для запроса нормализованного 64-разрядного значения счетчика циклов. В C или C++ используйте встроенную функцию __rdpmccntr64 для вывода соответствующего кода операции; в сборке используйте инструкцию __rdpmccntr64. Считывание счетчика циклов на процессоре Cortex-A9 занимает около 60 циклов.

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

См. также

Общие вопросы использования Visual C++ ARM
Обработка исключений ARM