链路寄存器 (LR) 是否受内联函数或裸函数的影响
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++
并且我不关心可移植性。
例如,如果我有以下带有 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 类架构中,它还会在异常输入时接收一个特殊的魔术值,当像返回地址一样使用时会触发异常返回,使异常处理程序看起来与常规函数完全相同。
输入函数后,编译器可以自由地将该值保存在其他地方,并将r14
用作另一个通用寄存器。实际上,如果它想要进行任何嵌套调用,则需要将值保存在某个地方。对于大多数编译器,任何非叶函数都会lr
作为序言的一部分推送到堆栈(并且通常利用能够在尾声中将其直接弹出回pc
以返回)。
确保函数调用不影响
LR
的正确方法是什么?
根据定义,函数调用会影响lr
- 否则它将是 goto,而不是调用(当然,尽管有尾部调用)。
re: update. 在下面留下我的旧答案,因为它回答了编辑前的原始问题。
__attribute__((naked))
基本上存在,因此您可以在 asm 中编写整个函数,asm
语句中,而不是在单独的.S
文件中。 编译器甚至不发出返回指令,您必须自己执行此操作。 将其用于内联函数是没有意义的(就像我在下面已经回答的那样)。
调用naked
函数将生成通常的调用序列,带有bl my_naked_function
,这当然将LR设置为指向bl
之后的指令。 naked
函数本质上是您在 asm 中编写的永不内联函数。 "序言"和"尾声"是保存和恢复被叫方保存的寄存器的指令,以及返回指令本身(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}nt" // keep the stack aligned for our callee by pushing a dummy register along with LR
"bl extfuncnt"
"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 的 asm 输出用于 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: 注释: 如果你只想设置一个 C 变量 = LR:
正如@Notlikethat指出的那样,LR
可能不持有退货地址。 因此,您可能希望__builtin_return_address(0)
获取当前函数的返回地址。 但是,如果您只是尝试保存寄存器状态,那么如果您希望此时正确恢复执行,则应保存/恢复该函数在 LR 中的任何内容:
#define get_lr(lr_val) asm ("mov %0, lr" : "=r" (lr_val))
这可能需要volatile
,以防止它在全程序优化期间被提升到调用树上。
这会导致额外的 mov 指令,而理想的顺序可能是存储 lr,而不是先复制到另一个 reg。 由于 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 的参数)。 内联毫无意义,因为您不知道编译器将参数放在哪里。
如果要内联某些 asm,请不要使用 naked
函数。 相反,请使用对输入/输出参数使用正确约束的内联函数。
x86 wiki 有一些很好的内联 asm 链接,它们并不都是特定于 x86 的。 例如,请参阅本答案末尾的 GNU 内联 asm 链接集合,以获取有关如何充分利用语法让编译器围绕您的 asm 片段制作尽可能高效的代码的示例。
- 内联如何影响模块接口中的成员函数
- 为什么返回类型的'const'限定符对标有 __forceinline/内联的函数没有影响?
- 重载运算符的范围是什么?它是否会影响作为类成员的集合的插入函数?
- 是否可以影响 C++ 中回调函数的局部变量?
- 通过默认复制构造函数比较 C++ 字符串是否会影响性能,原因为何?
- 隐式转换函数的返回对象时是否会影响性能?
- 来自重载或模板化函数的额外未使用代码的影响?
- "virtual"对C++析构函数有何影响?
- 睡眠影响 std::thread 调用哪个虚拟成员函数?
- "const"作为模板参数对移动构造函数的影响
- C :对输入验证函数进行编程的性能影响可以对其进行编程,而每次都可以将其编程
- 将函数及其实现移动到与主文件不同的文件(.hpp 和 .cpp)时,性能会受到很大影响
- 如何在保持多态性的同时,将成员函数添加到需要它的继承类中,而不会影响其他同级类?
- 以前的代码似乎会影响以后函数调用的时间
- 为什么移动构造函数会影响IS_ASSIGN
- 为什么删除复制构造函数会影响用户定义的默认构造函数?
- _T宏是否影响 std::string 构造函数
- 实现move构造函数如何影响返回值优化
- 定义多个名称相同但类型的函数如何影响C++编译器的性能?
- 对受内联构造函数影响的vtable错误的未定义引用