链接寄存器(LR)是否受内联函数或裸函数的影响?

时间:2016-03-17 16:33:49

标签: c++ assembly arm cortex-m bare-metal

我使用的是ARM Cortex-M4处理器。据我所知,LR(链接寄存器)存储当前正在执行的函数的返回地址。但是,内联和/或裸功能是否会影响它?

我正致力于实施简单的多任务处理。我想编写一些代码来保存执行上下文(将R0 - R12LR放到堆栈中),以便以后可以恢复。上下文保存后,我有一个SVC,因此内核可以安排另一个任务。当它决定再次安排当前任务时,它将恢复堆栈并执行BX LR。我问这个问题是因为我希望BX LR能够跳到正确的位置。

我们说我使用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属性whose documentation表示不会有编译器生成的序言/结尾序列。这到底是什么意思呢。裸函数是否仍会导致函数调用,是否会影响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

的正确方法是什么

2 个答案:

答案 0 :(得分:2)

  

据我了解,LR(链接寄存器)存储当前正在执行的函数的返回地址。

不,lr在执行blblx指令时只接收以下指令的地址。在M-class体系结构中,它还在异常进入时接收一个特殊的魔术值,当像返回地址一样使用时会触发异常返回,使异常处理程序看起来与常规函数完全相同。

输入函数后,编译器可以自由地将该值保存在其他位置,并将r14用作另一个通用寄存器。实际上,如果它想要进行任何嵌套调用,需要来保存值。对于大多数编译器而言,任何非叶子函数都会将lr作为序言的一部分推送到堆栈中(并且通常可以利用能够将其直接弹回到结尾的pc中来返回)。 / p>

  

确保函数调用不影响LR

的正确方法是什么

根据定义的函数调用影响lr - 否则它将是一个goto,而不是一个调用(当然是尾调用)。

答案 1 :(得分:1)

重新:更新。在下面留下我的旧答案,因为它在编辑之前回答了原始问题。

__attribute__((naked))基本上存在,因此您可以在asm中编写整个函数,在asm语句中而不是在单独的.S文件中。编译器甚至不发出返回指令,你必须自己做。将它用于内联函数是没有意义的(就像我在下面已经回答过的那样)。

调用naked函数将生成通常的调用序列,其中bl my_naked_function当然将LR设置为指向bl之后的指令。 naked函数本质上是一个用asm编写的永不内联函数。 “prologue”和“epilogue”是保存和恢复被调用者保存的寄存器以及返回指令本身(bx lr)的指令。

试一试,看看。很容易看到gcc的asm输出。我更改了函数名称以帮助解释发生了什么,并修复了语法(GNU C __attribute__扩展需要加倍的parens)。

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

asm output with gcc 4.8.2 -O2 for ARM(默认是拇指目标,我认为)。

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或内联函数中的堆栈。

re:comments:如果你只想设置一个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约束来为编译器提供该选项。

You could wrap this inside an inline function。宏中的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;
}

供参考:What are SP (stack) and LR in ARM?

naked always_inline功能无用。

文档说naked函数只能包含asm语句,只能包含“Basic”asm(没有操作数,所以你必须自己从ABI的正确位置获取args)。内联是没有意义的,因为你不知道编译器把你的args放在哪里。

如果要内联某些asm,请不要使用naked函数。而是使用对输入/输出参数使用正确约束的内联函数。

wiki有一些很好的内联asm链接,而且它们并不是x86特有的。例如,请参阅the collection of GNU inline asm links at the end of this answer,了解如何充分利用语法让编译器在asm片段周围尽可能高效地编写代码。