Что такое SP (стек) и LR в ARM?

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

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

edit: наконец-то выяснил, для чего предназначен LR, но до сих пор не получил того, для чего SP.

Ответы

Ответ 1

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}...

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

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

Ответ 2

SP - это регистр стека, ярлык для ввода r13. LR - это регистр ссылок для ярлыка для r14. И ПК - это счетчик программ, ярлык для ввода r15.

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

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

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

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

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

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

Традиционный (ну не весь путь к началу) синтаксис руки:

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

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

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

вы можете использовать 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/С++ и т.д. Обычно имеют "соглашение о вызове" или интерфейс приложения (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. позже последний pop, sp - 0x0FFC, который считывается, и значение, помещенное в r1, тогда указатель стека получает значение 0x1000, где оно начиналось.

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

Если вы программируете ARM-процессор, вы должны начать с определения (поставщик чипов должен сказать вам, что ARM не делает чипы, которые он делает ядрами, которые чип-продавцы помещают в свои фишки), в каком именно ядре у вас есть. Затем перейдите на веб-сайт руки и найдите ARM ARM для этого семейства и найдите TRM (техническое справочное руководство) для конкретного ядра, включая ревизию, если поставщик предоставил это (r2p0 означает версию 2.0 (двухточечная нуль, 2p0)), даже если есть новый rev, используйте руководство, которое идет с тем, которое поставщик использовал в своем дизайне. Не каждое ядро ​​поддерживает каждую команду или режим, TRM сообщает вам, какие режимы и инструкции поддерживаются ARM ARM, бросает на себя все функции для всего семейства процессоров, в которых живет это ядро. Обратите внимание, что ARM7TDMI - это ARMv4 NOT 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 core, multi gigahertz. Cortex-m едва тормозит барьер 100 МГц и имеет память, измеренную в килобайтах, хотя, вероятно, она запускает батарею в течение нескольких месяцев, если вы хотите, чтобы там, где коры - не так много.

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