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

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

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

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

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


person richq    schedule 04.11.2008    source источник
comment
Вот внешняя ссылка, которая может быть полезна. Введение в APCS, особенно некоторые разные имена для register использования.   -  person artless noise    schedule 15.04.2013


Ответы (5)


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

Из AAPCS, §5.1.1:

  • r0-r3 - регистры аргументов и временные регистры; r0-r1 также являются регистрами результатов
  • r4-r8 - регистры сохранения вызываемого абонента.
  • r9 может быть регистром сохранения вызываемого абонента или нет (в некоторых вариантах AAPCS это специальный регистр)
  • r10-r11 - регистры сохранения вызываемого абонента.
  • r12-r15 - специальные регистры

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

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


Терминология: «callee-save» является синонимом «энергонезависимый» или «сохраняемый вызов»: Что такое сохраненные регистры вызываемого и вызывающего абонента?
При вызове функции вы можете предположить, что значения в r4-r11 (кроме, возможно, r9) все еще присутствуют после ( сохраняемый вызов), но не для r0-r3 (call-clobbered / volatile).

person CesarB    schedule 04.11.2008
comment
Спасибо, это похоже на звонок. Я думаю, что первый r0-r4 в вашем списке - опечатка, верно? +1 (и, вероятно, лучший ответ, если не будет радикального поворота) - person richq; 04.11.2008
comment
Да, это была опечатка (и не единственная, но я исправил другие, прежде чем нажать «Отправить» в первый раз - по крайней мере, я надеюсь). - person CesarB; 04.11.2008
comment
Вы можете загрузить всю спецификацию ABI, сопроводительные документы и примеры кода в виде ZIP-архива с этой страницы. Zip-архив: infocenter.arm.com/help/topic /com.arm.doc.ihi0036b/bsabi.zip - person jww; 23.06.2011
comment
Думаю, гораздо легче запомнить, что вам нужно сохранять и восстанавливать r4-r11 на случай, если вы собираетесь их использовать; вот почему они сохраняются вызываемыми. - person ; 27.03.2012
comment
Чтобы расширить комментарий amorenoc: r4-r11 (возможно, за исключением r9) можно считать безопасным при вызове функции. r0-r3, вероятно, не будет сохранен после вызова функции, и, в зависимости от того, как выполняется связывание, не будет и r12 (который можно использовать как временный регистр). - person Leo; 14.04.2012
comment
Комментарий Алекса сбивает с толку, поскольку он с точки зрения вызываемого. Здесь обсуждается вопрос с точки зрения вызывающего абонента. Вызывающему абоненту НЕ нужно сохранять r4-r11 при вызове функции C. Функция C (вызываемый) сохранит эти регистры. Также почему никто не уточняет, нужно ли сохранять r9 звонилкой или нет? Я считаю, что для цепочки инструментов arm-eabi-gcc, r9 также сохраняется вызываемым пользователем. Кто может указать на источник информации, решающий вопрос r9? - person Sven; 13.08.2013
comment
Подводя итог: при вызове функции C необходимо сохранить регистры r0-r3, r12 (и, возможно, r9). По моему опыту, gcc использует r12 как регистр царапин внутри функции и, следовательно, он не сохраняется для вызываемого, даже если не используется взаимодействие руки / большого пальца. В случае взаимодействия компоновщик сгенерирует связующий код, который использует r12, если функция руки вызывает функцию большого пальца. - person Sven; 13.08.2013
comment
Я просто просматриваю этот документ PCS и сомневаюсь, что регистры переменных v1-v8 используются для сохранения локальных переменных. Если да, что произойдет, когда я выделю больше локальных переменных? Я не могу подключить стек и эти регистры ... - person Xavier Geoffrey; 12.10.2015

Соглашения о вызовах 32-битных ARM определены AAPCS

From the AAPCS, §5.1.1 Core registers:

  • r0-r3 - регистры аргументов и временные регистры; r0-r1 также являются регистрами результатов
  • r4-r8 - регистры сохранения вызываемого абонента.
  • r9 может быть регистром сохранения вызываемого абонента или нет (в некоторых вариантах AAPCS это специальный регистр)
  • r10-r11 - регистры сохранения вызываемого абонента.
  • r12-r15 - специальные регистры

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

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

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


Соглашения о вызовах 64-битных ARM указаны в AAPCS64.

General-purpose Registers section specifies what registers need be preserved.

  • r0 - r7 - регистры параметров / результатов.
  • r9 - r15 - временные регистры.
  • r19 - r28 - регистры, сохраненные вызываемым пользователем.
  • Все остальные (r8, r16 - r18, r29, r30, SP) имеют особое значение, и некоторые из них могут рассматриваться как временные регистры.

SIMD и регистры с плавающей запятой определяют неоновые регистры и регистры с плавающей запятой.

person Pavel P    schedule 29.03.2011

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

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

  • SP Указатель стека
  • r30 LR Регистр ссылок
  • r29 FP Указатель кадра
  • r19… r28 регистры, сохраненные вызываемым абонентом
  • r18 Регистр платформы, если необходимо; в противном случае временный реестр.
  • r17 IP1 Второй временный регистр внутрипроцедурного вызова (может использоваться для внешних вызовов и кода PLT); в остальное время может использоваться как временный регистр.
  • r16 IP0 Первый регистр царапин внутрипроцедурного вызова (может использоваться винировами вызова и кодом PLT); в остальное время может использоваться как временный регистр.
  • r9… r15 Временные регистры
  • r8 Регистр местоположения косвенного результата
  • r0… r7 Регистры параметров / результатов

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

Регистры r16 (IP0) и r17 (IP1) могут использоваться компоновщиком в качестве временного регистра между подпрограммой и любой вызываемой ею подпрограммой. Их также можно использовать в подпрограмме для хранения промежуточных значений между вызовами подпрограмм.

Роль регистра r18 зависит от платформы. Если платформе 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; вызывающая сторона несет ответственность за сохранение больших значений.

person auselen    schedule 13.04.2015

Ответы CesarB и Павла содержали цитаты из AAPCS, но остаются нерешенными вопросы. Сохраняет ли вызываемый r9? Что насчет r12? Что насчет r14? Кроме того, ответы были очень общими и не относились конкретно к инструментальной цепочке 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), которые могут повлиять на результаты.

person Sven    schedule 13.08.2013
comment
Ваш анализ неверен; lr всплывает как pc для более быстрого возврата. Ответ на ваш r9 вопрос находится в APCS. В этом документе он называется статической базой, а раздел Реентерабельный и не реентерабельный код является относительным. APCS поддерживает несколько конфигураций, но gcc обычно повторно используется без ограничений стека. В частности, в некоторых вариантах APCS есть специальные роли для sb/r9 и sl/r10. В других вариантах они могут использоваться как регистры, сохраненные вызываемым - person artless noise; 28.10.2013
comment
См. ссылку ARM и указатель кадра для получения подробной информации о pc и lr. r12 также известен как ip и может использоваться во время пролога и эпилога. Это изменчивый регистр. Это важно для подпрограмм, которые анализируют стек вызовов / фреймы. - person artless noise; 28.10.2013
comment
В каком смысле мой анализ относительно lr неверен? Я думаю, вы меня неправильно поняли. Во всяком случае, я представлял второй фрагмент кода сборки, поскольку первый выглядел так, как будто вызываемый объект был сохранен lr. Однако я думаю, что это не так. Да, во втором фрагменте lr отображается как pc для более быстрого возврата, и я не объяснял этого, но смысл представления второго фрагмента заключался в том, что он показывает, что lr не сохранен. - person Sven; 27.08.2014
comment
Верно, что lr восстанавливается до pc. Но неверно, что можно ожидать восстановления самого значения lr. Я не понимаю, как это может быть неправильно. То, что значение попадает в регистр, отличный от lr, совершенно не имеет отношения к вопросу, восстанавливается lr или нет. Вы правы, что набор регистров, который восстанавливается и не восстанавливается, может измениться при изменении опции -mabi. - person Sven; 30.08.2014
comment
Я вижу, что вы правы в гипотетическом смысле; Вы могли бы написать какой-нибудь ассемблер, который возвращал бы указатель на функцию в lr. Однако я не понимаю, что для вас даст исходное значение lr. Вы выполняете код, который был в lr при возврате, поэтому его исходное значение явно определяется выполнением кода. Павел подытожил, что r12-r15 - это специальные регистры. Значение lr при вызове будет значением pc при выходе. Вопрос, восстановлен ли lr или нет, кажется мне странным. Смотря где и будет ли листик или нет. - person artless noise; 30.08.2014
comment
Прямо сейчас я пишу ассемблерную оболочку для вложенных прерываний на ARM7TDMI. Сохраняется ли значение lr при вызове C-кода, важно, так как значение lr должно быть восстановлено до своего предыдущего значения с помощью деформации ассемблера. Так что знание того, спасен ли lr вызываемым, не так уж и гипотетично. - person Sven; 02.04.2015
comment
Ах, регистры хранятся для прерываний именно по этой причине. В любом случае мы согласны не соглашаться. - person artless noise; 02.04.2015
comment
Это именно то, что я искал - способ узнать, какие регистры сохраняются конкретными настройками компилятора, которые я использую для своего проекта. Спасибо! - person TonyK; 07.02.2017
comment
@artlessnoise: если бы существовало соглашение, согласно которому функции должны возвращаться с тем же значением в LR, с которым они были вызваны, тогда что-то вроде if (foo) do { } while(bar()); могло бы обрабатываться как bl foo / test r0,r0 / bne bar, а возврат из bar передал бы выполнение обратно инструкции после bl. На практике ситуации, когда было бы полезно гарантировать значение в r14 при возврате функции, достаточно редки, поэтому лучше позволить ему содержать произвольное значение. - person supercat; 06.11.2020

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

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

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

person Frik    schedule 13.07.2017