簡體   English   中英

鏈接寄存器(LR)受內聯或裸函數影響嗎?

[英]Is the Link Register (LR) affected by inline or naked functions?

我正在使用ARM Cortex-M4處理器。 據我了解, LR (鏈接寄存器)存儲當前執行函數的返回地址。 但是,內聯和/或裸函數會對其產生影響嗎?

我正在實現簡單的多任務處理。 我想寫一些代碼來保存執行上下文(將R0 - R12LR到堆棧中),以便以后可以恢復。 保存上下文后,我有了一個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_inlinenaked 這有什么區別嗎?

確保函數調用不影響LR的正確方法是哪種?

據我了解, LR (鏈接寄存器)存儲當前執行函數的返回地址。

不, lr在執行blblx指令時僅接收以下指令的地址。 在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或內聯函數中的堆棧弄亂。


重新:注釋:如果您只想設置一個C變量= 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函數。 而是使用對輸入/輸出參數使用正確約束的內聯函數。

Wiki具有一些不錯的內聯asm鏈接,並且它們並不都特定於x86。 例如,請參閱此答案末尾的GNU內聯asm鏈接集合,以獲取有關如何充分利用語法以使編譯器圍繞asm片段盡可能高效地編寫代碼的示例。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM