[英]Is the Link Register (LR) affected by inline or naked functions?
我正在使用ARM Cortex-M4處理器。 據我了解, LR
(鏈接寄存器)存儲當前執行函數的返回地址。 但是,內聯和/或裸函數會對其產生影響嗎?
我正在實現簡單的多任務處理。 我想寫一些代碼來保存執行上下文(將R0
- R12
和LR
到堆棧中),以便以后可以恢復。 保存上下文后,我有了一個SVC
因此內核可以安排另一個任務。 當決定再次計划當前任務時,它將恢復堆棧並執行BX LR
。 我問這個問題是因為我希望BX LR
跳到正確的位置。
假設我使用arm-none-eabi-g++
而我並arm-none-eabi-g++
可移植性。
例如,如果我的以下代碼具有always_inline
屬性,則由於編譯器將對其進行內聯,因此在生成的機器代碼中將不會進行函數調用,因此LR
不受影響,對嗎?
__attribute__((always_inline))
inline void Task::saveContext() {
asm volatile("PUSH {R0, R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, LR}");
}
然后,還有一個naked
屬性, 其文檔說它將沒有編譯器生成的序言/結尾序列 。 這到底是什么意思呢。 裸函數仍然會導致函數調用,並且會影響LR
嗎?
__attribute__((naked))
void saveContext() {
asm volatile("PUSH {R0, R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, LR}");
}
此外,出於好奇,如果函數標有兩個會發生什么always_inline
和naked
? 這有什么區別嗎?
確保函數調用不影響LR
的正確方法是哪種?
據我了解,
LR
(鏈接寄存器)存儲當前執行函數的返回地址。
不, lr
在執行bl
或blx
指令時僅接收以下指令的地址。 在M類體系結構中,它在異常輸入時也會收到一個特殊的magic值,當像返回地址一樣使用時,它將觸發異常返回,使異常處理程序看起來與常規函數完全相同。
輸入函數后,編譯器可以自由地將該值保存在其他位置,並將r14
用作另一個通用寄存器。 實際上,如果要進行任何嵌套調用,就需要將值保存在某個地方。 對於大多數編譯器,任何非葉子函數都將lr
作為序言的一部分推入堆棧(並且經常利用能夠將其直接彈出結尾語中的pc
中以返回)。
確保函數調用不影響
LR
的正確方法是哪種?
根據定義,函數調用會影響lr
否則它將是goto,而不是調用(盡管有尾調用)。
回復:更新。 將我的舊答案留在下面,因為它會在編輯之前回答原始問題。
__attribute__((naked))
基本存在,因此您可以在asm
語句內部的asm
中而不是單獨的.S
文件中編寫整個函數。 編譯器甚至不會發出返回指令,您必須自己執行。 將其用於內聯函數沒有任何意義(就像我已經在下面回答的那樣)。
調用naked
函數將生成帶有bl my_naked_function
的常規調用序列,該序列當然bl my_naked_function
LR設置為指向bl
之后的指令。 naked
函數本質上是您在asm中編寫的永不內聯函數。 “ prologue”和“ epilogue”是保存和恢復被調用方保存的寄存器的指令,以及返回指令本身( bx lr
)。
試試看。 查看gcc的asm輸出很容易。 我更改了函數名稱以幫助解釋發生了什么,並修復了語法(GNU C __attribute__
擴展名需要加倍的括號)。
extern void extfunc(void);
__attribute__((always_inline))
inline void break_the_stack() { asm volatile("PUSH LR"); }
__attribute__((naked))
void myFunc() {
asm volatile("PUSH {r3, LR}\n\t" // keep the stack aligned for our callee by pushing a dummy register along with LR
"bl extfunc\n\t"
"pop {r3, PC}"
);
}
int foo_simple(void) {
extfunc();
return 0;
}
int foo_using_inline(void) {
break_the_stack();
extfunc();
return 0;
}
帶有gcc 4.8.2 -O2 for ARM的asm輸出 (我認為默認是拇指目標)。
myFunc(): # I followed the compiler's foo_simple example for this
PUSH {r3, LR}
bl extfunc
pop {r3, PC}
foo_simple():
push {r3, lr}
bl extfunc()
movs r0, #0
pop {r3, pc}
foo_using_inline():
push {r3, lr}
PUSH LR
bl extfunc()
movs r0, #0
pop {r3, pc}
額外的推LR表示我們正在將錯誤的數據彈出到PC中。 在這種情況下,也許是LR的另一個副本,但是我們返回的是經過修改的堆棧指針,因此調用者將中斷。 除非您正在嘗試執行某種二進制檢測操作,否則請不要將LR或內聯函數中的堆棧弄亂。
正如@Notlikethat指出的那樣, LR
可能不保存返回地址。 因此,您可能希望__builtin_return_address(0)
來獲取當前函數的返回地址。 但是,如果您只是想保存寄存器狀態,則如果希望此時正確恢復執行,則應該保存/恢復LR中的功能。
#define get_lr(lr_val) asm ("mov %0, lr" : "=r" (lr_val))
在整個程序優化過程中,這可能需要保持可volatile
以防止其被提升到調用樹中。
當理想的順序是存儲lr而不是首先復制到另一個reg時,這會導致額外的mov指令。 由於ARM使用不同的指令進行reg-reg移動和存儲到內存,因此您不能僅對輸出操作數使用rm
約束來為編譯器提供該選項。
您可以將其包裝在內聯函數中 。 宏中的GNU C語句表達式也可以工作,但是內聯函數應該可以:
__attribute__((always_inline)) void* current_lr(void) { // This should work correctly when inlined, or just use the macro
void* lr;
get_lr(lr);
return lr;
}
供參考: ARM中的SP(堆棧)和LR是什么?
naked
always_inline
函數沒有用。 文檔說, naked
函數只能包含asm
語句,並且只能包含“基本” asm(沒有操作數,因此您必須自己從ABI的正確位置獲取args)。 內聯表示零意義,因為您不知道編譯器將args放在哪里。
如果要內聯一些asm,請不要使用naked
函數。 而是使用對輸入/輸出參數使用正確約束的內聯函數。
x86 Wiki具有一些不錯的內聯asm鏈接,並且它們並不都特定於x86。 例如,請參閱此答案末尾的GNU內聯asm鏈接集合,以獲取有關如何充分利用語法以使編譯器圍繞asm片段盡可能高效地編寫代碼的示例。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.