Что такое SP (стек) и LR в ARM? - PullRequest
66 голосов
/ 23 ноября 2011

Я читаю определения снова и снова, и я до сих пор не понимаю, что такое SP и LR в ARM?Я понимаю, что ПК (он показывает адрес следующей инструкции), SP и LR, вероятно, похожи, но я просто не понимаю, что это такое.Не могли бы вы мне помочь?

изменить: если бы вы могли объяснить это с помощью примеров, это было бы превосходно.для чего LR, все еще не понимая, для чего предназначен SP.

Ответы [ 2 ]

82 голосов
/ 23 ноября 2011

LR - регистр связи , используемый для хранения адреса возврата для вызова функции.

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

Из ссылки на архитектуру ARM:

SP, указатель стека

Регистр R13 используется в качестве указателя на активный стек.

В коде Thumb большинство инструкций не имеют доступа к SP. Единственный инструкции, которые могут получить доступ к SP, предназначены для использования SP в качестве указатель стека. Использование SP для любых целей, кроме как в стеке указатель устарел. Примечание Использование SP для любых целей, кроме как Указатель стека, вероятно, нарушает требования операционной системы, отладчики и другие программные системы, заставляя их неисправность.

LR, Реестр ссылок

Регистр R14 используется для хранения адреса возврата из подпрограммы. В в других случаях LR может использоваться для других целей.

Когда инструкция BL или BLX выполняет вызов подпрограммы, LR устанавливается на обратный адрес подпрограммы. Чтобы выполнить возврат подпрограммы, скопируйте LR вернуться к счетчику программ. Обычно это делается в одном из двух способы, после ввода подпрограммы с инструкцией BL или BLX:

• Вернитесь с инструкцией BX LR.

• При входе в подпрограмму сохраните LR в стек с инструкцией вида: PUSH {, LR} и используйте соответствующую инструкцию для возврата: POP {, PC} ...

Эта ссылка дает пример тривиальной подпрограммы.

Вот пример того, как регистры сохраняются в стеке до вызова, а затем возвращаются назад для восстановления их содержимого.

42 голосов
/ 23 ноября 2011

SP - регистр стека, ярлык для ввода r13. LR - ссылка, зарегистрируйте ярлык для r14. А на ПК есть счетчик программ ярлык для набора r15.

Когда вы выполняете вызов, называемый инструкцией связи по ответвлению, bl, обратный адрес помещается в r14, регистр связи. ПК счетчика программы изменяется на адрес, на который вы переходите.

Есть несколько указателей стека в традиционных ядрах ARM (исключение составляют серии cortex-m), когда вы нажимаете прерывание, например, вы используете другой стек, чем при работе на переднем плане, вам не нужно менять свой Код просто используйте sp или r13 как обычно, аппаратное обеспечение сделало переключение для вас и использует правильный, когда он декодирует инструкции.

Традиционный набор инструкций ARM (не большой палец) дает вам свободу использования стека при переходе от низших адресов к старшим или с высоких адресов на низкие. компиляторы и большинство людей устанавливают высокий указатель стека и уменьшают его с высоких адресов до младших. Например, может быть, у вас есть ram от 0x20000000 до 0x20008000, вы устанавливаете скрипт компоновщика для сборки / запуска вашей программы / используете 0x20000000 и устанавливаете указатель стека на 0x20008000 в коде запуска, по крайней мере указатель стека системы / пользователя, который вы должны разделить память для других стеков, если вам нужно / используйте их.

Стек - это просто память. Процессоры обычно имеют специальные инструкции чтения / записи в памяти, которые основаны на ПК, а некоторые - на стеке. Как правило, стековые единицы обычно называются push и pop, но не обязательно (как в случае с традиционными инструкциями для arm).

Если перейти к http://github.com/lsasim, я создал учебный процессор и у меня есть учебник по ассемблеру. Где-то там я прохожу обсуждение стеков. Это не процессор руки, но история та же самая, она должна переводить непосредственно на то, что вы пытаетесь понять на руке или большинстве других процессоров.

Скажем, например, у вас есть 20 переменных, которые вам нужны в вашей программе, но только 16 регистров минус как минимум три из них (sp, lr, pc) специального назначения. Вы должны будете держать некоторые из своих переменных в оперативной памяти. Допустим, r5 содержит переменную, которую вы используете достаточно часто, поэтому вы не хотите хранить ее в оперативной памяти, но есть одна часть кода, где вам действительно нужен другой регистр, чтобы сделать что-то, а r5 не используется, вы можете сохранить r5 на стек с минимальными усилиями, пока вы повторно используете r5 для чего-то другого, а затем легко восстанавливаете его.

Традиционный (ну, не до самого начала) синтаксис arm:

...
stmdb r13!,{r5}
...temporarily use r5 for something else...
ldmia r13!,{r5}
...

stm - это несколько магазинов, вы можете сохранить более одного регистра за раз, до всех из них в одной инструкции.

дБ означает уменьшение до этого, это нисходящий стек от старших адресов к младшим.

Вы можете использовать r13 или sp здесь, чтобы указать указатель стека. Эта конкретная инструкция не ограничивается операциями со стеком, может использоваться для других целей.

The! означает обновление регистра r13 новым адресом после его завершения, и здесь stm может использоваться для операций, не связанных со стеком, так что вы можете не захотеть изменять регистр базового адреса, оставьте! в этом случае выкл.

Затем в скобках {} перечислите регистры, которые вы хотите сохранить, через запятую.

ldmia обратный, ldm означает кратную нагрузку. ia означает приращение после, а остальное такое же, как у stm

Таким образом, если ваш указатель стека был равен 0x20008000, когда вы нажали на команду stmdb, видя, что в списке есть один 32-битный регистр, он будет уменьшаться, прежде чем использовать его значение в r13, поэтому 0x20007FFC, тогда он записывает r5 в 0x20007FFC в памятисохраняет значение 0x20007FFC в r13.Позже, если у вас нет ошибок, когда вы попадете в инструкцию ldmia, r13 имеет 0x20007FFC, в списке r5 есть один регистр.Таким образом, он считывает память в 0x20007FFC, помещает это значение в r5, ia означает увеличение после того, как 0x20007FFC увеличивает один размер регистра до 0x20008000 и!означает записать это число в r13, чтобы завершить инструкцию.

Почему бы вам использовать стек вместо просто фиксированной ячейки памяти?Прелесть вышеописанного в том, что r13 может быть где угодно, где он может быть 0x20007654, когда вы запускаете этот код, или 0x20002000 или что-то еще, и код все еще функционирует, даже лучше, если вы используете этот код в цикле или с рекурсией, он работает и для каждого уровняиз рекурсии вы идете, вы сохраняете новую копию r5, у вас может быть 30 сохраненных копий в зависимости от того, где вы находитесь в этом цикле.и, по мере того как он разворачивается, он откладывает все копии обратно по желанию.с одной фиксированной ячейкой памяти, которая не работает.Это переводит непосредственно к коду C в качестве примера:

void myfun ( void )
{
   int somedata;
}

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

Глобальная переменная не найдена в стеке.

Возвращаясь ...

Скажем, вы хотели реализовать и вызватьэта функция у вас будет некоторый код / ​​функция, в которой вы находитесь при вызове функции myfun.Функция myfun хочет использовать r5 и r6, когда она работает с чем-то, но она не хочет уничтожать то, что кто-то вызвал, используя r5 и r6, поэтому на время myfun () вы захотите сохранить эти регистры в стеке.Аналогичным образом, если вы посмотрите на инструкцию ссылки ветвления (bl) и регистр связи lr (r14), есть только один регистр связи, если вы вызываете функцию из функции, вам нужно будет сохранять регистр связи при каждом вызове, иначе вы не сможете вернуться.

...
bl myfun
    <--- the return from my fun returns here
...


myfun:
stmdb sp!,{r5,r6,lr}
sub sp,#4 <--- make room for the somedata variable
...
some code here that uses r5 and r6
bl more_fun <-- this modifies lr, if we didnt save lr we wouldnt be able to return from myfun
   <---- more_fun() returns here
...
add sp,#4 <-- take back the stack memory we allocated for the somedata variable
ldmia sp!,{r5,r6,lr}
mov pc,lr <---- return to whomever called myfun.

Так что, надеюсь, вы увидите как использование стека, так и регистр ссылок.Другие процессоры делают то же самое по-другому.например, некоторые помещают возвращаемое значение в стек, а когда вы выполняете функцию возврата, она знает, куда возвращаться, вытягивая значение из стека.Компиляторы C / C ++ и т. Д. Обычно имеют «соглашение о вызовах» или интерфейс приложения (ABI и EABI являются именами для тех, что определены в ARM).если каждая функция следует соглашению о вызове, помещает передаваемые ей параметры в вызываемые функции в правильных регистрах или в стеке в соответствии с соглашением.И каждая функция следует правилам относительно того, какие регистры не должны сохранять содержимое и какие регистры должны сохранять содержимое, тогда вы можете иметь функции, вызывающие функции, вызывающие функции и выполняющие рекурсию и все виды вещей, до тех пор, покастек не уходит так глубоко, что он попадает в память, используемую для глобалов и кучи, и тому подобное, вы можете вызывать функции и возвращаться из них весь день.Приведенная выше реализация myfun очень похожа на то, что вы увидите в компиляторе.

В ARM теперь много ядер, и несколько наборов инструкций серии cortex-m работают немного по-другому, поскольку в них нет набора режимов и разных указателей стека. И при выполнении инструкций большого пальца в режиме большого пальца вы используете инструкции push и pop, которые не дают вам свободы использовать любой регистр, такой как stm, он использует только r13 (sp), и вы не можете сохранить все регистры, только определенное их подмножество. популярные манипуляторы позволяют использовать

push {r5,r6}
...
pop {r5,r6}

в коде руки, а также в коде большого пальца. Для кода руки он кодирует правильный stmdb и ldmia. (в режиме большого пальца у вас также нет выбора, когда и где использовать db, уменьшать до и, например, увеличивать после).

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

push {r5,r6,r7}
...
pop {r2,r3}
...
pop {r1}

при условии, что между этими инструкциями нет других модификаций указателя стека, если вы помните, что значение sp будет уменьшено на 12 байт для push, скажем, от 0x1000 до 0x0FF4, r5 будет записано в 0xFF4, r6 в 0xFF8 и r7 в 0xFFC указатель стека изменится на 0x0FF4. первый pop примет значение 0x0FF4 и поместит его в r2, затем значение 0x0FF8, а в r3 указатель стека получит значение 0x0FFC. после последнего всплывающего окна sp - это 0x0FFC, который считывается, и значение помещается в r1, указатель стека затем получает значение 0x1000, с которого оно началось.

ARM ARM, Справочное руководство по архитектуре ARM (infocenter.arm.com, справочные руководства, найдите для ARMv5 и загрузите его, это традиционное ARM ARM с инструкциями ARM и thumb), содержит псевдокод для ldm и stm ARM istructions для полной картины о том, как они используются. Точно так же вся книга о руке и как ее программировать. Перед вами глава модели программистов проведет вас через все регистры во всех режимах и т. Д.

Если вы программируете процессор ARM, вам следует начать с определения (производитель чипов должен сказать вам, что ARM не делает чипы, а делает ядра, которые производители чипов вставляют в свои чипы), точно какое ядро ​​у вас есть. Затем перейдите на веб-сайт arm и найдите ARM ARM для этого семейства и найдите TRM (техническое справочное руководство) для конкретного ядра, включая ревизию, если поставщик ее предоставил (r2p0 означает ревизию 2.0 (две точки ноль, 2p0)), даже если есть более новая версия, используйте руководство, которое идет с тем, которое поставщик использовал в их дизайне. Не каждое ядро ​​поддерживает все инструкции или режимы, которые TRM сообщает вам о поддерживаемых режимах и инструкциях. ARM ARM предлагает набор функций для всего семейства процессоров, в которых живет это ядро. Обратите внимание, что ARM7TDMI - это ARMv4, а не ARMv7. ARM9 не является ARMv9. ARMvNUMBER - это фамилия ARM7, ARM11 без v - это имя ядра. Более новые ядра имеют имена вроде Cortex и mpcore вместо ARMNUMBER, что уменьшает путаницу. Конечно, им пришлось добавить путаницу обратно, сделав ARMv7-m (cortex-MNUMBER) и ARMv7-a (Cortex-ANUMBER), которые очень разные семейства, одно для тяжелых нагрузок, настольных ПК, ноутбуков и т. Д. Другое для микроконтроллеров, часов и мигающих лампочек на кофеварке и подобных вещах. Google Beagleboard (Cortex-A) и доска обнаружения стоимостной линии stm32 (Cortex-M), чтобы почувствовать различия. Или даже на плате open-rd.org, которая использует несколько ядер с тактовой частотой более гигагерца или более новую версию Tegra 2 от nvidia, суперскейлер той же сделки, ядро ​​muti, мульти гигагерц. Cortex-m с трудом преодолевает барьер в 100 МГц и имеет объем памяти, измеряемый в килобайтах, хотя он, вероятно, работает от батареи в течение нескольких месяцев, если вы хотите, чтобы Cortex-a не так много.

извините за очень длинный пост, надеюсь, он будет полезен.

...