Какие регистры следует сохранить в соглашении о вызовах ARM C?

StackOverflow https://stackoverflow.com/questions/261419

Вопрос

Прошло много времени с тех пор, как я в последний раз кодировал ассемблер руки, и я немного подзабыл детали.Если я вызываю функцию C из Arm, мне нужно беспокоиться только о сохранении r0-r3 и lr, верно?

Если функция C использует какие-либо другие регистры, отвечает ли она за сохранение их в стеке и их восстановление?Другими словами, компилятор сгенерирует код для функций C.

Например, если я использую r10 в функции ассемблера, мне не нужно помещать его значение в стек или в память и извлекать/восстанавливать его после вызова C, не так ли?

Это для Arm-eabi-gcc 4.3.0.

Это было полезно?

Решение

Это зависит от АБИ для платформы, для которой вы компилируете.В Linux есть два ARM ABI;старый и новый.AFAIK, новый (EABI) на самом деле является AAPCS ARM.Полные определения EABI в настоящее время доступны. здесь, в информационном центре ARM.

От AAPCS, §5.1.1:

  • р0-р3 — это аргументы и рабочие регистры; р0-р1 также являются регистрами результатов
  • р4-р8 регистры сохранения вызываемого абонента
  • р9 может быть регистром сохранения вызываемого абонента или нет (в некоторых вариантах AAPCS это специальный регистр)
  • р10-р11 регистры сохранения вызываемого абонента
  • р12-р15 это специальные регистры

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

Редактировать: Какой компилятор вы используете, не имеет значения;В частности, gcc можно настроить для нескольких разных ABI, и его даже можно изменить в командной строке.Просмотр кода пролога/эпилога, который он генерирует, не так уж и полезен, поскольку он адаптирован для каждой функции. и компилятор может использовать другие способы сохранения регистра (например, сохранение его в середине функции).

Другие советы

Чтобы добавить недостающую информацию по регистрам NEON:

От ААПКС, §5.1.1 Основные регистры:

  • р0-р3 — это аргументы и рабочие регистры; р0-р1 также являются регистрами результатов
  • р4-р8 регистры сохранения вызываемого абонента
  • р9 может быть регистром сохранения вызываемого абонента или нет (в некоторых вариантах AAPCS это специальный регистр)
  • р10-р11 регистры сохранения вызываемого абонента
  • р12-р15 это специальные регистры

Из AAPCS, §5.1.2.1 Соглашения об использовании регистров VFP:

  • с16–с31 (d8–d15, q4–q7) должен быть сохранен
  • s0–s15 (d0–d7, q0–q3) и d16–d31 (q8–q15) не нужно сохранять

Исходное сообщение:
неоновые регистры-для-сохранения

Для 64-битного ARM, A64 (из Стандарта вызова процедур для 64-битной архитектуры ARM)

Для набора команд A64 доступен тридцать один 64-битный регистр общего назначения (целочисленный);они помечены р0-р30.В 64-битном контексте эти регистры обычно называются именами х0-х30;в 32-битном контексте регистры указываются с помощью w0-w30.Кроме того, регистр указателя стека, СП, может использоваться с ограниченным количеством инструкций.

  • СП Указатель стека
  • р30 LR Регистрация ссылок
  • р29 FP Указатель кадра
  • р19…р28 Регистры, сохраненные вызываемым абонентом
  • р18 Реестр платформы, если необходимо;в противном случае временный регистр.
  • р17 IP1 Второй временный регистр внутрипроцедурного вызова (может использоваться винирами вызовов и кодом PLT);В другое время может использоваться в качестве временного регистра.
  • р16 IP0 Первый реестр царапин внутрипроцедурной зоны (может использоваться с помощью виниров вызовов и кода PLT);В другое время может использоваться в качестве временного регистра.
  • р9…р15 Временные регистры
  • р8 Регистр местоположения косвенного результата
  • р0…р7 Регистры параметров/результатов

Первые восемь регистров, р0-р7, используются для передачи значений аргументов в подпрограмму и для возврата значений результатов из функции.Их также можно использовать для хранения промежуточных значений внутри процедуры (но, как правило, только между вызовами подпрограмм).

Регистры р16 (ИП0) и р17 (ИП1) может использоваться компоновщиком в качестве временного регистра между подпрограммой и любой вызываемой им подпрограммой.Их также можно использовать внутри подпрограммы для хранения промежуточных значений между вызовами подпрограмм.

Роль регистра р18 зависит от платформы.Если ABI платформы необходим специальный регистр общего назначения для переноса межпроцедурного состояния (например, контекста потока), тогда он должен использовать этот регистр для этой цели.Если у платформы ABI таких требований нет, то ей следует использовать r18 в качестве дополнительного временного регистра.Спецификация ABI платформы должна документировать использование этого регистра.

SIMD

64-битная архитектура ARM также имеет еще тридцать два регистра. v0-v31, который может использоваться SIMD и операциями с плавающей запятой.Точное имя регистра изменится, указывая размер доступа.

Примечание: В отличие от AArch32, в AArch64 128-битные и 64-битные представления SIMD и регистра с плавающей запятой не перекрывают несколько регистров в более узком представлении. поэтому q1, d1 и s1 относятся к одной и той же записи в банке регистров.

Первые восемь регистров, v0-v7, используются для передачи значений аргументов в подпрограмму и для возврата значений результатов из функции.Их также можно использовать для хранения промежуточных значений внутри процедуры (но, как правило, только между вызовами подпрограмм).

Регистры v8-v15 должен сохраняться вызываемым объектом при вызове подпрограммы;остальные регистры (v0-v7, v16-v31) не нужно сохранять (или должен сохранять вызывающий объект).Кроме того, только нижние 64 бита каждого значения хранятся в v8-v15 необходимо сохранить;ответственность за сохранение больших значений лежит на вызывающей стороне.

В ответах CesarB и Pavel приведены цитаты из AAPCS, но остаются открытые вопросы.Сохраняет ли вызываемый абонент r9?А что насчет р12?А что насчет р14?Более того, ответы были очень общими и не относились конкретно к инструментальной цепочке Arm-eabi, как было запрошено.Вот практический подход, позволяющий выяснить, какие регистры сохраняются вызываемым абонентом, а какие нет.

Следующий код C содержит встроенный ассемблерный блок, который утверждает, что изменяет регистры r0–r12 и r14.Компилятор сгенерирует код для сохранения регистров, необходимых ABI.

void foo() {
  asm volatile ( "nop" : : : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14");
}

Используйте командную строку arm-eabi-gcc-4.7 -O2 -S -o - foo.cи добавьте переключатели для вашей платформы (например, -mcpu=arm7tdmi например).Команда выведет сгенерированный ассемблерный код на STDOUT.Это может выглядеть примерно так:

foo:
    stmfd   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr}
    nop
    ldmfd   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr}
    bx  lr

Обратите внимание, что сгенерированный компилятором код сохраняет и восстанавливает r4-r11.Компилятор не сохраняет r0-r3, r12.То, что он восстанавливает r14 (псевдоним lr), является чистой случайностью, поскольку по опыту я знаю, что код выхода также может загрузить сохраненный lr в r0, а затем выполнить «bx r0» вместо «bx lr».Либо добавив -mcpu=arm7tdmi -mno-thumb-interwork или с помощью -mcpu=cortex-m4 -mthumb мы получаем немного другой ассемблерный код, который выглядит так:

foo:
    stmfd   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr}
    nop
    ldmfd   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, pc}

Опять же, r4-r11 сохраняются и восстанавливаются.Но r14 (псевдоним lr) не восстанавливается.

Обобщить:

  • r0-r3 нет вызываемый абонент сохранен
  • r4-r11 сохраняются вызываемым абонентом
  • r12 (псевдоним IP) нет вызываемый абонент сохранен
  • r13 (псевдоним sp) сохраняется вызываемым абонентом
  • r14 (псевдоним lr) — это нет вызываемый абонент сохранен
  • r15 (псевдоним pc) — это программный счетчик, которому присваивается значение lr перед вызовом функции.

Это справедливо, по крайней мере, для настроек по умолчанию в Arm-eabi-gcc.Существуют ключи командной строки (в частности, ключ -mabi), которые могут повлиять на результаты.

Существует также разница, по крайней мере, в архитектуре Cortex M3 для вызова функций и прерываний.

Если произойдет прерывание, оно автоматически переведет R0-R3, R12, LR, ПК в стек и при возврате из IRQ автоматически POP. Если вы используете другие регистры в процедуре IRQ, вы должны вручную вставить их в стек.

Я не думаю, что эти автоматические PUSH и POP созданы для вызова функции (инструкция перехода). Если соглашение гласит, что R0-R3 может использоваться только в качестве аргумента, результата или чистых регистров, поэтому нет необходимости сохранять их до вызова функции, потому что не должно быть никакого значения, используемого позже после возврата функции. Но так же, как и в случае прерывания, вы должны хранить все остальные регистры ЦП, если используете их в своей функции.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top