单片机 > 单片机程序设计 > 详情

ARM64与ARM32 的Linux程序区别在哪里

发布时间:2023-03-02 发布时间:
|

当ARM为其架构引入64位支持时,它旨在与以前的32位软件兼容。但对于Linux程序员来说,仍然存在一些可能影响代码行为的重大差异。以下是我们发现的一些内容以及我们为他们开发的解决方法。


我本来打算将这篇文章称为“ARMv8 for Linux程序员的新功能?”然而,我认为“有什么不同”更为贴切。而且,仅仅为了记录,“ARMv8-A”是指AArch64,带有A64指令集,也称为arm64或ARM64。我在示例中使用了AArch64寄存器,但我所描述的许多问题也发生在ARMv8-A 32位执行状态。


为了帮助构建此处讨论的问题,让我开始通过给出一些我们在撤销时所拥有的代码库的背景知识。我们的核心技术是记录和重放引擎,它通过将所有非确定性输入记录到程序并使用即时编译(JIT)来跟踪程序状态来工作。我们的技术始于x86(32和64)当我们开始调整它以适应AArch64时,我已经在ARM 32位上获得了相当完整,成熟的支持。几乎所有的低悬的水果都被抓住了之后我加入了公司(以及很多相当高的树,为了公平)我们在转向ARMv8时遇到了一些棘手的问题。


这让我想到了我的第一个简单但可能有用的观察结果:ARM64与ARM 32位(又名AArch32)相比,与x86更相似。 ARM64仍然非常RISC(尽管加密加速指令确实导致RISC架构中的眉毛抬起)。所以我不打算试图涵盖x86和ARM版本之间的许多差异。我也不想重新审视AArch32和AArch64之间的差异 - 已经有很好的资源来探索这些差异。


此外,许多ARM与ARM64资源都集中在指令集和架构差异上。这些差异与大多数Linux用户空间应用程序开发人员并不十分相关,超出了非常明显的范围,例如“你的指针更大。”但是,正如我们发现的那样,对于Linux用户空间开发人员来说存在重要差异,其中四个我将会在这里讨论。这些差异分为几类,一些属于多个类别。类别是:

由于迁移使用相当新的内核版本而产生的差异。

由于体系结构和指令集(这与用户空间程序员有关)的差异。

Ptrace 差异。我们经常使用 ptrace ,所以这对我们来说非常重要。

我将在下一节中尝试使用以下格式:

该区域的简要说明。

有什么不同?为什么会有所不同?(有时通过查看一些装配说明比通过罗嗦的描述更容易理解行为的变化,所以我会提供那段代码。)

我们是如何遇到它的?

我们是如何克服它的?

在哪里可以找到更多信息。

1。对 ptrace 的更改

ptrace 为用户空间程序提供了进程跟踪功能。

有一个编号对 ptrace()接受的请求的更改。这些更改产生了最令人愉快的所有不兼容性:编译错误。我们的错误报告是针对未定义的符号PTRACE_GETREGS(对于通用寄存器),PTRACE_GETFPREGS(对于浮点和SIMD寄存器),以及PTRACE_GETHBPREGS(对于硬件断点寄存器),以及这些请求的SET版本。

ptrace 的 man 页面在解决方面毫无帮助这些错误,所以我们挖得更深。我们看了一下内核源代码,结果发现通常存在独立于体系结构的 ptrace 代码路径(kernel/ptrace.c中的ptrace_request()),以及独立的体系结构依赖路径(例如arch/arm/kernel/ptrace.c中的arch_ptrace()。虽然arm64版本有一个用于AArch32应用程序的compat_arch_ptrace,但arm64 arch_ptrace()直接调用ptrace_request()并且不添加任何其他 ptrace 请求类型。


解决方案是使用PTRACE_GETREGSET和PTRACE_SETREGSET具有各种不同的参数来读取这些寄存器。


这是GETREGS样式请求和最接近的等效GETREGSET请求的表。通过 addr ptrace()参数的不同参数获取不同的REGSET。

ARM 32位AArch64
GETREGSNT_PRSTATUS
GETFPREGSNT_PRFREG
GETHPBREGSNT_ARM_HW_BREAK
NT_ARM_HW_WATCH

表1. ARM 32位和最接近的等效AArch64 ptrace请求。


请注意,NT_ARM_HW_BREAK和NT_ARM_HW_WATCH在GETREGSET请求中的行为相同。


使用GETREGSET并不像使用GETREGS那么简单。对于像这样的GETREGS请求:

ptrace(PTRACE_GETREGS,0,0,regs);

GETREGSET看起来像这样:

struct {void * buf; size_t len;} my_iovec = {regs,sizeof(* regs)};
ptrace(PTRACE_GETREGSET,0,(void *)NT_PRSTATUS,& my_iovec);

注意也是我已经说过“最接近的等效GETREGSET请求。”当然,AArch64寄存器组与ARM 32位寄存器不同,但两者之外的寄存器组之间存在更多差异。


图图1示出了从ARM 32位GETREGS和AArch64 GETREGSET指令返回的寄存器的图。

图1. GETREGS和GETREGSET。


熟悉AArch64的人可能会注意到GETREGSET我们已经获得了“cpsr”注册,但硬件架构没有。 GETREGSET返回的内容已经从AArch64上可单独访问的字段合成为类似cpsr的布局。


两者之间更显着的差异是GETREGSET缺少orig_r0(或orig_x0)。这种缺乏与系统调用有关。在ARM 32位上,系统调用编号放在r7中,并且系统调用参数在系统调用(SVC)指令之前放在参数寄存器r0-r3中。从系统调用返回的值位于r0(根据通常的APCS,在特殊情况下为r7)。内核从 syscall 返回后,orig_r0将原始的第一个参数提供给 syscall (已被返回值覆盖)。


I实际上不知道“正常”应用程序应该使用什么用于这个原始的第一个参数。我们使用它来支持 restart_syscall ,其中返回值为ERESTART_RESTARTBLOCK。


不幸的是,缺乏orig_x0对我们来说是个问题,我们还没有在任何情况下解决。如果我们已经记录了系统调用的条目,那么我们就拥有了所需的所有信息。但是,如果我们在 restart_syscall 期间附加了,那么我们就不知道x0的原始值。我们唯一的选择是允许内核重新启动 syscall ,但这次重启对我们来说是低效的,因为我们无法优化系统调用的录制。

回到GETREGS与GETREGSET的主题:GETHBPREGS和NT_ARM_HW_BREAK也有很大不同。对于GETHBPREGS请求,使用 ptrace 调用中的 addr 字段来请求特定的硬件断点寄存器。 NT_ARM_HW_BREAK返回所有硬件断点寄存器。


『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
MSP430低功耗模式-while循环失效