ARM 组装。将 r13(堆栈指针)用作通用寄存器是否安全?

ARM assembly. Is it safe to use r13 (stack pointer) as a general purpose register?

我正在编写一个极其优化的叶函数并使其 运行 更快 我想将 R13 用作通用寄存器。我通过在使用它之前将它移动到一个 VFP 寄存器来保留 R13,并且在从函数返回之前我通过将它移回来恢复它。它看起来像这样:

/* Start of the function */
push { r4 - r12, r14 }
vmov s0, r13
/* Body of the function. Here I use R13
 * as a general purpose register */
vmov r13, s0
pop { r4 - r12, r14 }
bx lr

并且有效。但我读到一些操作系统假定 R13 总是用作堆栈指针,将其用作通用寄存器会导致崩溃。我还应该说,此函数仅用于 运行 Android (Linux)。谢谢!

显然,如果您已经在使用 所有 其他 GP 寄存器,包括 lr,您应该只考虑这个,并且不能将您的一些工作转移到 NEON 寄存器,例如即使您只关心低 32 位,也使用压缩整数。

(将 SIMD regs 用于更多标量整数通常仅在有一组独立的值不与算法中的其他值交互时才有用,并且您不需要对它们进行分支或使用它们作为指针。在某些 ARM CPU 上,int 和 SIMD 之间的传输速度很慢。)

这是非常不标准的,甚至只有在用户 space 中才是安全的,而不是内核


如果您安装了任何信号处理程序,当其中一个信号到达时,您的堆栈指针必须有效。 (这是异步的。)

除了信号处理程序之外,Linux 中的 user-space 堆栈指针没有其他异步用法。(除非您正在使用 GDB 进行调试并使用 print foo(123),其中 foo 是目标进程中的一个函数。)

正如在 Can I use rsp as a general purpose register 的评论中提到的(这个问题的 x86-64 等价物),即使是信号也有一个解决方法:

使用sigaltstack设置替代堆栈,并在安装处理程序时在sigaction的标志中指定SA_ONSTACK

正如@Timothy 指出的那样,如果您的 SP 临时值可能是一个整数,恰好 "point" 进入 alt 堆栈,信号调度机制将假定这是一个嵌套信号并且 不会 修改 SP(因为在实际的嵌套信号情况下会覆盖第一个信号处理程序仍在使用的堆栈)。因此,您可能 push 远离 SP 进入未映射的页面,除非您 分配所需的两倍,并且只将上半部分传递给 sigaltstack. (对于 return 没有做太多事情的简单信号处理程序,可能只有 2k 或 4k)。

即使对于嵌套信号,这也应该是安全的:只有最外层的信号处理程序可以从靠近替代堆栈的底部开始,并使用超出实际替代堆栈的一些已分配 space。如果 SP 仍在 altstack 中,另一个信号将在其下方使用 space。或者,如果 SP 超出了 altstack,它将使用 altstack 的顶部。

或者如果您的任何 GP 寄存器需要作为指针,您可以通过使用 SP 保存指向绝对不是 alt 堆栈的其他东西的指针来避免这种过度分配的需要。 如果调试器将当前 SP 用于某事,或者如果您将 altstack 机制弄错,让它成为一个有效的指针会使您容易损坏而不是错误。但这只是故障模式的不同:两者都是灾难性的。


硬件中断将状态保存在内核堆栈上,而不是用户-space 堆栈。如果他们使用用户堆栈:

  1. user-space 可能会因为 SP 无效而导致 OS 崩溃。
  2. user-space 可以通过让另一个 user-space 线程修改内核的堆栈数据(包括 return 地址。)
  3. 来获得内核权限

(一个进程的所有 user-space 线程共享同一页 table,并且可以 read/write 彼此的堆栈映射。)

Linux/Android 与没有虚拟内存或严格执行特权分离的轻量级 RTOS 有很大不同。

当你的代码执行时上下文switch/irq会触发,OS/hw可能会假设R13是TOS,所以它会保存它的想法,它可以恢复 TOS 当它恢复执行时。

这可能是您遇到的问题。

一个明智的方法是使代码片段变得关键,并以某种方式强制系统 tick/irq 挂起,直到例程 finishes/R13 恢复。

如果你真的需要额外的寄存器,你最好使用 LR (R14)。