Link 寄存器 (LR) 是否受内联或裸函数影响?
Is the Link Register (LR) affected by inline or naked functions?
我使用的是 ARM Cortex-M4 处理器。据我了解,LR
(link寄存器)存储的是当前执行函数的return地址。但是,内联 and/or 裸函数会影响它吗?
我正在努力实现简单的多任务处理。我想写一些代码来保存执行上下文(将 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
属性whose documentation说它不会有编译器生成的prologue/epilogue序列。这到底是什么意思呢。裸函数是否仍会导致函数调用并影响 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
的正确方法是什么?
回复:更新。在下面留下我的旧答案,因为它在编辑之前回答了原始问题。
__attribute__((naked))
基本上存在,因此您可以在 asm 中编写 whole 函数,在 asm
语句中而不是在单独的 .S
文件中.编译器甚至不发出 return 指令,你必须自己做。将它用于内联函数没有意义(就像我已经在下面回答的那样)。
调用 naked
函数将生成通常的调用序列,带有 bl my_naked_function
,这当然会将 LR 设置为指向 bl
之后的指令。 naked
函数本质上是您在 asm 中编写的永不内联函数。 “prologue”和“epilogue”是保存和恢复被调用者保存的寄存器的指令,以及 return 指令本身 (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;
}
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 的另一个副本,但我们 return 使用修改后的堆栈指针,因此调用者会中断。不要乱用内联函数中的 LR 或堆栈,除非您正在尝试执行某种二进制检测操作。
回复:评论:如果你只想设置一个 C 变量 = LR:
正如@Notlikethat 指出的那样,LR
可能不包含 return 地址。所以你可能希望 __builtin_return_address(0)
得到当前函数的 return 地址。但是,如果您只是想保存寄存器状态,那么如果您希望在此时正确恢复执行,那么您应该 save/restore 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
语句,并且只能包含“基本”asm(没有操作数,因此您必须自己从 ABI 的正确位置获取参数) .内联毫无意义,因为您不知道编译器将您的参数放在哪里。
如果要内联某些 asm,请不要使用 naked
函数。相反,使用内联函数,该函数对 input/output 参数使用正确的约束。
x86 wiki has some good inline asm links, and they're not all specific to x86. For example, see 有关如何充分利用语法的示例,让编译器围绕您的 asm 片段生成尽可能高效的代码。
As far as I understand, the LR
(link register) stores the return address of the currently executing function.
不,lr
只是在执行 bl
或 blx
指令时接收以下指令的地址。在 M-class 架构中,它在异常入口时也接收到一个特殊的 magic 值,当像 return 地址一样使用时会触发异常 return ,使异常处理程序看起来完全一样作为常规函数。
一旦函数被输入,编译器就可以自由地将这个值保存在别处并将r14
用作另一个通用寄存器。实际上,如果要进行任何嵌套调用,它 需要 将值保存在某处。对于大多数编译器,任何非叶函数都会将 lr
压入堆栈作为序言的一部分(并且经常利用能够直接将其弹出到 pc
的结尾到 return).
Which is the correct way to ensure that a function call does not affect the LR
?
一个函数调用根据定义影响lr
- 否则它将是一个goto,而不是一个调用(尽管有尾调用,当然)。
我使用的是 ARM Cortex-M4 处理器。据我了解,LR
(link寄存器)存储的是当前执行函数的return地址。但是,内联 and/or 裸函数会影响它吗?
我正在努力实现简单的多任务处理。我想写一些代码来保存执行上下文(将 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
属性whose documentation说它不会有编译器生成的prologue/epilogue序列。这到底是什么意思呢。裸函数是否仍会导致函数调用并影响 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
的正确方法是什么?
回复:更新。在下面留下我的旧答案,因为它在编辑之前回答了原始问题。
__attribute__((naked))
基本上存在,因此您可以在 asm 中编写 whole 函数,在 asm
语句中而不是在单独的 .S
文件中.编译器甚至不发出 return 指令,你必须自己做。将它用于内联函数没有意义(就像我已经在下面回答的那样)。
调用 naked
函数将生成通常的调用序列,带有 bl my_naked_function
,这当然会将 LR 设置为指向 bl
之后的指令。 naked
函数本质上是您在 asm 中编写的永不内联函数。 “prologue”和“epilogue”是保存和恢复被调用者保存的寄存器的指令,以及 return 指令本身 (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;
}
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 的另一个副本,但我们 return 使用修改后的堆栈指针,因此调用者会中断。不要乱用内联函数中的 LR 或堆栈,除非您正在尝试执行某种二进制检测操作。
回复:评论:如果你只想设置一个 C 变量 = LR:
正如@Notlikethat 指出的那样,LR
可能不包含 return 地址。所以你可能希望 __builtin_return_address(0)
得到当前函数的 return 地址。但是,如果您只是想保存寄存器状态,那么如果您希望在此时正确恢复执行,那么您应该 save/restore 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
语句,并且只能包含“基本”asm(没有操作数,因此您必须自己从 ABI 的正确位置获取参数) .内联毫无意义,因为您不知道编译器将您的参数放在哪里。
如果要内联某些 asm,请不要使用 naked
函数。相反,使用内联函数,该函数对 input/output 参数使用正确的约束。
x86 wiki has some good inline asm links, and they're not all specific to x86. For example, see
As far as I understand, the
LR
(link register) stores the return address of the currently executing function.
不,lr
只是在执行 bl
或 blx
指令时接收以下指令的地址。在 M-class 架构中,它在异常入口时也接收到一个特殊的 magic 值,当像 return 地址一样使用时会触发异常 return ,使异常处理程序看起来完全一样作为常规函数。
一旦函数被输入,编译器就可以自由地将这个值保存在别处并将r14
用作另一个通用寄存器。实际上,如果要进行任何嵌套调用,它 需要 将值保存在某处。对于大多数编译器,任何非叶函数都会将 lr
压入堆栈作为序言的一部分(并且经常利用能够直接将其弹出到 pc
的结尾到 return).
Which is the correct way to ensure that a function call does not affect the
LR
?
一个函数调用根据定义影响lr
- 否则它将是一个goto,而不是一个调用(尽管有尾调用,当然)。