简体   繁体   English

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

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

I'm using an ARM Cortex-M4 processor. 我正在使用ARM Cortex-M4处理器。 As far as I understand, the LR (link register) stores the return address of the currently executing function. 据我了解, LR (链接寄存器)存储当前执行函数的返回地址。 However, do inline and/or naked functions affect it? 但是,内联和/或裸函数会对其产生影响吗?

I'm working on implementing simple multitasking. 我正在实现简单的多任务处理。 I'd like to write some code that saves the execution context (pusing R0 - R12 and LR to the stack) so that it can be restored later. 我想写一些代码来保存执行上下文(将R0 - R12LR到堆栈中),以便以后可以恢复。 After the context save, I have an SVC so the kernel can schedule another task. 保存上下文后,我有了一个SVC因此内核可以安排另一个任务。 When it decide to schedule the current task again, it'd restore the stack and execute BX LR . 当决定再次计划当前任务时,它将恢复堆栈并执行BX LR I'm asking this question because I'd like BX LR to jump to the correct place. 我问这个问题是因为我希望BX LR跳到正确的位置。

Let's say I use arm-none-eabi-g++ and I'm not concerned with portability. 假设我使用arm-none-eabi-g++而我并arm-none-eabi-g++可移植性。

For example, if I have the following code with the always_inline attribute, since the compiler will inline it, then there is not gonna be a function call in the resulting machine code, so the LR is unaffected, right? 例如,如果我的以下代码具有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}");
}

Then, there is also the naked attribute whose documentation says that it will not have prologue/epilogue sequences generated by the compiler . 然后,还有一个naked属性, 其文档它将没有编译器生成的序言/结尾序列 What exactly does that mean. 这到底是什么意思呢。 Does a naked function still result in a function call and does it affect the LR ? 裸函数仍然会导致函数调用,并且会影响LR吗?

__attribute__((naked))
void saveContext() {
    asm volatile("PUSH {R0, R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, LR}");
}

Also, out of curiosity, what happens if a function is marked with both always_inline and naked ? 此外,出于好奇,如果函数标有两个会发生什么always_inlinenaked Does that make a difference? 这有什么区别吗?

Which is the correct way to ensure that a function call does not affect the LR ? 确保函数调用不影响LR的正确方法是哪种?

As far as I understand, the LR (link register) stores the return address of the currently executing function. 据我了解, LR (链接寄存器)存储当前执行函数的返回地址。

Nope, lr simply receives the address of the following instruction upon execution of a bl or blx instruction. 不, lr在执行blblx指令时仅接收以下指令的地址。 In the M-class architecture, it also receives a special magic value upon exception entry, which will trigger an exception return when used like a return address, making exception handlers look exactly the same as regular functions. 在M类体系结构中,它在异常输入时也会收到一个特殊的magic值,当像返回地址一样使用时,它将触发异常返回,使异常处理程序看起来与常规函数完全相同。

Once the function has been entered, the compiler is free to save that value elsewhere and use r14 as just another general-purpose register. 输入函数后,编译器可以自由地将该值保存在其他位置,并将r14用作另一个通用寄存器。 Indeed, it needs to save the value somewhere if it wants to make any nested calls. 实际上,如果要进行任何嵌套调用,就需要将值保存在某个地方。 With most compilers any non-leaf function will push lr to the stack as part of the prologue (and often take advantage of being able to pop it straight back into pc in the epilogue to return). 对于大多数编译器,任何非叶子函数都将lr作为序言的一部分推入堆栈(并且经常利用能够将其直接弹出结尾语中的pc中以返回)。

Which is the correct way to ensure that a function call does not affect the LR ? 确保函数调用不影响LR的正确方法是哪种?

A function call by definition affects lr - otherwise it would be a goto, not a call (tail-calls notwithstanding, of course). 根据定义,函数调用会影响lr否则它将是goto,而不是调用(尽管有尾调用)。

re: update. 回复:更新。 Leaving my old answer below, since it answers the original question before the edit. 将我的旧答案留在下面,因为它会在编辑之前回答原始问题。

__attribute__((naked)) basically exists so you can write the whole function in asm, inside asm statements instead of in a separate .S file. __attribute__((naked))基本存在,因此您可以在asm语句内部的asm中而不是单独的.S文件中编写整个函数。 The compiler doesn't even emit a return instruction, you have to do that yourself. 编译器甚至不会发出返回指令,您必须自己执行。 It doesn't make sense to use this for inline functions (like I already answered below). 将其用于内联函数没有任何意义(就像我已经在下面回答的那样)。

Calling a naked function will generate the usual call sequence, with a bl my_naked_function , which of course sets LR to point to the instruction after the bl . 调用naked函数将生成带有bl my_naked_function的常规调用序列,该序列当然bl my_naked_function LR设置为指向bl之后的指令。 A naked function is essentially a never-inline function that you write in asm. naked函数本质上是您在asm中编写的永不内联函数。 "prologue" and "epilogue" are the instructions that save and restore callee-saved registers, and the return instruction itself ( bx lr ). “ prologue”和“ epilogue”是保存和恢复被调用方保存的寄存器的指令,以及返回指令本身( bx lr )。


Try it and see. 试试看。 It's easy to look at gcc's asm output. 查看gcc的asm输出很容易。 I changed your function names to help explain what's going on, and fixed the syntax (The GNU C __attribute__ extension requires doubled parens). 我更改了函数名称以帮助解释发生了什么,并修复了语法(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 (default is a thumb target, I think). 带有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}

The extra push LR means we're popping the wrong data into PC. 额外的推LR表示我们正在将错误的数据弹出到PC中。 Maybe another copy of LR, in this case, but we're returning with a modified stack pointer, so the caller will break. 在这种情况下,也许是LR的另一个副本,但是我们返回的是经过修改的堆栈指针,因此调用者将中断。 Don't mess with LR or the stack in an inline function, unless you're trying to do some kind of binary instrumentation thing. 除非您正在尝试执行某种二进制检测操作,否则请不要将LR或内联函数中的堆栈弄乱。


re: comments: if you just want to set a C variable = LR: 重新:注释:如果您只想设置一个C变量= LR:

As @Notlikethat points out, LR might not hold the return address. 正如@Notlikethat指出的那样, LR可能不保存返回地址。 So you might want __builtin_return_address(0) to get the return address of the current function. 因此,您可能希望__builtin_return_address(0)来获取当前函数的返回地址。 However, if you're just trying to save register state, then you should save/restore whatever the function has in LR if you hope to correctly resume execution at this point: 但是,如果您只是想保存寄存器状态,则如果希望此时正确恢复执行,则应该保存/恢复LR中的功能。

#define get_lr(lr_val)  asm ("mov %0, lr" : "=r" (lr_val))

This might need to be volatile to stop it from being hoisted up the call tree during whole-program optimization. 在整个程序优化过程中,这可能需要保持可volatile以防止其被提升到调用树中。

This leads to an extra mov instruction when perhaps the ideal sequence would be to store lr, rather than copy to another reg first. 当理想的顺序是存储lr而不是首先复制到另一个reg时,这会导致额外的mov指令。 Since ARM uses different instructions for reg-reg move vs. store to memory, you can't just use a rm constraint for the output operand to give the compiler that option. 由于ARM使用不同的指令进行reg-reg移动和存储到内存,因此您不能仅对输出操作数使用rm约束来为编译器提供该选项。

You could wrap this inside an inline function . 您可以将其包装在内联函数中 A GNU C statement-expression in a macro would also work, but an inline function should be fine: 宏中的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;
}

For reference: What are SP (stack) and LR in ARM? 供参考: ARM中的SP(堆栈)和LR是什么?


A naked always_inline function is not useful. naked always_inline函数没有用。

The docs say a naked function can only contain asm statements, and only "Basic" asm (without operands, so you have to get args from the right place for the ABI yourself). 文档说, naked函数只能包含asm语句,并且只能包含“基本” asm(没有操作数,因此您必须自己从ABI的正确位置获取args)。 Inlining that makes zero sense, because you won't know where the compiler put your args. 内联表示零意义,因为您不知道编译器将args放在哪里。

If you want to inline some asm, don't use a naked function. 如果要内联一些asm,请不要使用naked函数。 Instead, use an inline function that uses correct contraints for input/output parameters. 而是使用对输入/输出参数使用正确约束的内联函数。

The wiki has some good inline asm links, and they're not all specific to x86. Wiki具有一些不错的内联asm链接,并且它们并不都特定于x86。 For example, see the collection of GNU inline asm links at the end of this answer for examples of how to make good use of the syntax to let the compiler make as efficient code as possible around your asm fragment. 例如,请参阅此答案末尾的GNU内联asm链接集合,以获取有关如何充分利用语法以使编译器围绕asm片段尽可能高效地编写代码的示例。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM