Одна функция может вызывать другую функцию. Это несколько усложняет процесс управления вызовами функции. Например, мы имее следующую упрощенную конструкцию
.global _start _start: BL outerfunc MOV X0, 5 outerfunc: BL innerfunc RET innerfunc: RET
Здесь вызывается функция outerfunc, и, как и ожидается, инструкция BL
копирует адрес следующей инструкции в регистр LR (он же регистр X30).
Но в outerfunc
вызывается другая функция - innerfunc
. В этом случае инструкция BL
опять же копирует адрес следующей инструкции (теперь адрес следующей инструкции в
функции outerfunc
) в регистр LR и тем самым затирает ранее сохраненный адрес. И таким образом, мы не сможем возвратиться из функции outerfunc
.
Соответственно напрашивается решение, что необходимо сохранять всю цепочку адресов возврата. И для этой цели можно использовать стек.
Если функция outerfunc
вызывает другие функции, то она должна сохранить содержимое регистра LR в стек, а после выполнения вложенных функций
загрузить обратно из стека сохраненный адрес. В общем случае это выглядит так:
.global _start _start: BL outerfunc // вызываем функцию outerfunc MOV X0, 5 // для теста устанавливаем код возврата - 5 MOV X8, #93 // устанавливаем функцию Linux для выхода из программы SVC 0 // Вызываем функцию Linux outerfunc: STR LR, [SP, #-16]! // сохраняем в стеке текущий адрес из регистра LR BL innerfunc // вызываем функцию innerfunc LDR LR, [SP], #16 // извлекаем из стека адрес в регистр LR RET // вложенная функция innerfunc: RET
Причем, как и вообщем случае, проблема касается не только регистра LR, но всех остальных регистров, которые может изменить вложенная функции и которые используются во внешнем коде.