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


77

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

редагувати: якби ви могли пояснити це на прикладах, це було б чудово.

редагувати: нарешті зрозумів, для чого призначений LR, все ще не отримуючи того, для чого призначений SP.


3
Стек не є специфічним для ARM, (майже) кожен процесор і контролер має стек. secure.wikimedia.org/wikipedia/en/wiki/Call_stack
starblue

Пов’язане: ARM Link і покажчик кадру . Покажчик кадру fpпрацює з sp. У x86 , fpбуде bp; це також загальне поняття у викликах функцій, регістр для резервування локальної змінної.
бездумний шум

Відповіді:


89

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

Це посилання дає приклад тривіальної підпрограми.

Ось приклад того, як реєстри зберігаються у стеку до дзвінка, а потім з’являються назад, щоб відновити їх вміст.


Дякую, нарешті я зрозумів, для чого призначений LR, все ще не отримуючи того, що SP ...
good_evening

Що означає "стек"? Реєстри? Що? Не могли б ви дати мені простий приклад ІП, будь ласка?
good_evening

1
@hey Стек - це місце, де ви зберігаєте змінні, які не можете помістити в регістри. Зазвичай змінні, які мають якусь локальність через те, як працює стек. Ви можете прочитати більше про це тут en.wikipedia.org/wiki/Stack_(abstract_data_type) . Також ви на STACKoverflow, як ви не знаєте, що це?
Ісус Рамос,

@hey Я додав кілька речень, щоб спробувати дати вам деяку інтуїцію щодо того, що таке стек.
Гай Сіртон,

Просто хотів сказати, що, на жаль, обидва ваші посилання вже мертві.
hak8or

46

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

Коли ви виконуєте дзвінок, який називається інструкцією посилання на гілку, bl, адреса повернення розміщується в r14, реєстрі посилань. комп'ютерний лічильник ПК змінено на адресу, на яку Ви розгалужуєтесь.

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

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

Стек - це лише пам’ять. Процесори, як правило, мають спеціальні інструкції з читання / запису в пам’яті, які базуються на ПК, а деякі на основі стеку. Як мінімум, стекові називаються push and pop, але це не повинно бути (як і у традиційних інструкціях з рук).

Якщо ви заходите на http://github.com/lsasim, я створив навчальний процесор і маю підручник з мови асемблерів. Десь там я проходжу дискусію про стеки. Це НЕ процесор зброї, але історія така сама, вона повинна перекладатись безпосередньо на те, що ви намагаєтеся зрозуміти на плечі або більшості інших процесорів.

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

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

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

stm is store multiple, ви можете одночасно зберігати більше одного реєстру, до всіх їх в одній інструкції.

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, тобто означає збільшення після того, як 0x20007FFC збільшує розмір одного реєстру до 0x20008000 і! означає записати це число на r13, щоб завершити інструкцію.

Чому ви використовуєте стек замість просто фіксованого місця в пам'яті? Ну, краса вищесказаного полягає в тому, що r13 може бути де завгодно, це може бути 0x20007654, коли ви запускаєте цей код або 0x20002000 або що завгодно, і код все ще функціонує, ще краще, якщо ви використовуєте цей код у циклі або з рекурсією, це працює, і для кожного рівня після рекурсії ви зберігаєте нову копію r5, у вас може бути 30 збережених копій залежно від того, де ви знаходитесь у цьому циклі. і при розгортанні повертає всі копії назад за бажанням. з одним фіксованим місцем пам'яті, яке не працює. Це перекладається безпосередньо на код C як приклад:

void myfun ( void )
{
   int somedata;
}

У такій програмі на зразок, що змінна 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}

в коді руки, а також коді великого пальця. Для коду arm він кодує належні 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. перший поп прийме значення в 0x0FF4 і помістить це в r2, потім значення в 0x0FF8 і помістить, що в r3 вказівник стека отримує значення 0x0FFC. пізніше останнього спливаючого sp є 0x0FFC, який зчитується, і значення розміщується в r1, після чого покажчик стека отримує значення 0x1000, звідки він і почався.

ARM ARM, ARM Architectural Reference Manual (infocenter.arm.com, довідкові посібники, знайти той для ARMv5 та завантажити його, це традиційне ARM ARM з інструкціями ARM та thumb) містить псевдокод для інструкцій ldm та stm 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 (кора-НОМЕР) та ARMv7-a (Cortex-ANUMBER), які є дуже різними сім'ями, одна для великих навантажень, робочих столів, ноутбуків тощо, інша - для мікроконтролерів, годинників та миготливих вогнів на кавоварці тощо. google beagleboard (Cortex-A) та дошка виявлення рядків значення stm32 (Cortex-M), щоб відчути відмінності. Або навіть дошка open-rd.org, яка використовує декілька ядер, що перевищують гігагерц, або новішу tegra 2 від nvidia, той самий супер-скалер, муті-ядро, мульти-гігагерц.

вибачте за дуже довгий пост, сподіваюся, він буде корисним.


7
Мене заінтригував ваш проект github для вивчення збірки, але, схоже, вашого проекту немає. Чи є у вас його заміна? :)
Дейв

1
Я вважаю, що поточною адресою проекту є github.com/dwelch67/lsasim (діє на сьогодні, 7 вересня 2020 р.).
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.