Что такое SP (стек) и LR в ARM?
Я читаю определения снова и снова, и я до сих пор не понимаю, что такое SP и LR в ARM? Я понимаю ПК (он показывает адрес следующей инструкции), SP и LR, вероятно, похожи, но я просто не понимаю, что это такое. Не могли бы вы мне помочь?
редактировать: если бы вы могли объяснить это с помощью примеров, это было бы превосходно.
редактировать: наконец выяснил, для чего LR, все еще не понимая, для чего SP.
2 ответа
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} ...
SP - это регистр стека, ярлык для ввода r13. LR - ссылка, зарегистрируйте ярлык для r14. А на ПК есть счетчик программ, ярлык для набора текста r15.
Когда вы выполняете вызов, называемый инструкцией связи по ответвлению bl, обратный адрес помещается в r14, регистр связи. программный счетчик ПК изменяется на адрес, на который вы переходите.
Есть несколько указателей стека в традиционных ядрах ARM (исключение составляют серии cortex-m), когда вы нажимаете прерывание, например, если вы используете другой стек, чем при работе на переднем плане, вам не нужно менять код, просто используйте sp или r13 как обычно, аппаратное обеспечение сделало переключение для вас и использует правильное, когда оно декодирует инструкции.
Традиционный набор инструкций ARM (не большой палец) дает вам свободу использования стека при переходе от младших адресов к старшим или с высоких адресов на низкие. компиляторы и большинство людей устанавливают высокий указатель стека и уменьшают его с высоких адресов до более низких. Например, может быть, у вас есть ram от 0x20000000 до 0x20008000, вы устанавливаете скрипт компоновщика для сборки / запуска вашей программы / используете 0x20000000 и устанавливаете указатель стека на 0x20008000 в вашем коде запуска, по крайней мере указатель стека системы / пользователя, вы должны разделить память для других стеков, если вам нужно / использовать их.
Стек - это просто память. Процессоры обычно имеют специальные инструкции чтения / записи в памяти, которые основаны на ПК, а некоторые - на стеке. Стековые как минимум обычно называются push и pop, но не обязательно (как с традиционными инструкциями для рук).
Если вы зайдете на 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 хранится несколько элементов, за один раз можно сохранить несколько регистров, вплоть до всех из них в одной инструкции.
DB означает декремент раньше, это стек, перемещающийся вниз от старших адресов к младшим.
Вы можете использовать r13 или sp здесь, чтобы указать указатель стека. Эта конкретная инструкция не ограничивается операциями со стеком, может использоваться для других целей.
! означает обновление регистра r13 новым адресом после его завершения, и здесь stm может использоваться для операций, не связанных со стеком, так что вы можете не захотеть изменять регистр базового адреса, оставьте! в этом случае
Затем в скобках { } перечислите регистры, которые вы хотите сохранить, через запятую.
ИДМИЯ обратная, ЛДМ означает нагрузку, кратную. 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, уменьшение до и, ia, увеличение после).
Нет, вам абсолютно не обязательно использовать одни и те же регистры, и вам не нужно соединять одинаковое количество регистров.
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 для полная картина относительно того, как они используются. Точно так же вся книга о руке и как ее программировать. Перед вами глава модели программистов проведет вас через все регистры во всех режимах и т. Д.
Если вы программируете процессор ARM, вам следует начать с определения (производитель чипов должен сказать вам, что ARM не делает чипы, он делает ядра, которые производители чипов вставляют в свои чипы), точно, какое ядро у вас есть. Затем перейдите на веб-сайт arm и найдите ARM ARM для этого семейства и найдите TRM (техническое справочное руководство) для конкретного ядра, включая ревизию, если поставщик ее предоставил (r2p0 означает ревизию 2.0 (две точки ноль, 2p0)), даже если есть более новая версия, используйте руководство, которое идет с тем, которое поставщик использовал в их дизайне. Не каждое ядро поддерживает все инструкции или режимы, которые TRM сообщает вам о поддерживаемых режимах и инструкциях. ARM ARM предлагает множество возможностей для всего семейства процессоров, в которых живет это ядро. Обратите внимание, что ARM7TDMI - это ARMv4, а не ARMv7, также ARM9 не является ARMv9. ARMvNUMBER - это семейное имя ARM7, ARM11 без av - основное имя. Более новые ядра имеют имена вроде 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 не так уж и много.
извините за очень длинный пост, надеюсь, что это полезно.