x86 Heaven's Gate技术在ARM架构上的等效实现方法
ARM架构同进程混合运行32位与64位代码的实现方式
ARM架构(ARMv8及后续支持64位的版本)的执行状态切换逻辑和x86有本质区别,你没法像x86那样靠跨段远调用在用户态直接完成32/64位状态切换,具体规则和落地方法如下:
- 先讲底层硬件逻辑:ARMv8定义了两套完全独立的执行态,分别是跑ARM64指令集的AArch64(64位)、跑A32/Thumb指令集的AArch32(32位)。两种状态下的寄存器模型、寻址范围、栈帧规则、系统调用表全不兼容。和x86靠代码段描述符标志位控制执行态的设计不一样,ARM的执行状态由PSTATE寄存器的特权位
nRW控制,这个位只能在内核态(EL1及以上异常级别)修改,用户态没有直接写的权限,普通的跳转、调用指令根本碰不到这个位,自然没法直接切态。 - 所有跨32/64位的状态切换必须由内核参与完成,硬件唯一支持的用户态跨态入口是异常返回(ERET)路径:内核在处理系统调用、异常、信号返回的时候,会根据之前保存的SPSR(程序状态保存寄存器)里的
nRW位值,决定返回用户态之后进AArch64还是AArch32跑。 - 以Linux系统为例,实际工程里能落地的混跑方案,都是靠信号返回机制实现跨态切换,操作步骤很明确:
- 进程随便以哪种状态启动都行,比如先跑AArch64,只要内核给进程开了双态兼容权限就可以。
- 提前把要跑的异态代码(比如32位代码)、给异态用的栈、相关数据都映射到进程虚拟地址的低4GB范围里——AArch32态只能寻址32位地址空间,不在0~4GB范围内的内存它根本访问不到。
- 需要切到异态执行的时候,手动构造对应格式的信号返回帧,在帧里的SPSR字段设好目标执行态的标志位,把返回PC改成异态代码的入口地址,配好异态要用的栈指针、传参寄存器,最后调用
rt_sigreturn进内核,内核做完合法性校验之后,就会按你设置的状态返回用户态,直接开始跑异态代码。 - 异态代码跑完要切回原状态的时候,重复一遍构造信号帧、调sigreturn的流程就行。
- 几个必须注意的坑:
- 跨态的时候硬件不会自动帮你转上下文,你得自己按两套架构的传参规则手动处理参数:AArch64用X0X7传参,栈要16字节对齐;AArch32用R0R3传参,栈8字节对齐就行。
- 不少ARM处理器从硬件上就砍了AArch32支持,比如Apple M系列芯片、部分服务器级Cortex核心,这类硬件上根本跑不了32位ARM代码,别想混跑的事。
- 部分系统从软件层面禁了AArch32,比如新版macOS、iOS,就算硬件支持也跑不了32位程序。
- 两种架构的实现差异可以直接看代码对比,区别非常明显:
; x86方案:用户态直接靠远调用切态,完全不需要内核参与 BITS 32 ; 执行32位逻辑 call 0x33:foo ; 跨段远调用直接切到64位 ; 继续跑32位逻辑 BITS 64 foo: ; 执行64位逻辑 retf ; 远返回直接切回32位
// ARM方案:必须通过内核的信号返回路径切态,没有用户态直接切换的可能 // 以下是AArch64态切到AArch32的简化逻辑示意 // 1. 提前把32位代码、32位栈映射到低4G地址区间 // 2. 构造rt_sigreturn帧:设置SPSR.nRW=AArch32,PC=a32_entry,SP=分配好的32位栈顶 // 3. 调用rt_sigreturn进入内核,由内核完成状态切换 // ... 内核返回后就进入AArch32态开始执行 a32_entry: // 执行32位业务逻辑 // 要切回64位时,重复构造信号帧的流程,调用sigreturn切回即可
别尝试在用户态直接写PSTATE寄存器改执行态,这类操作会直接触发权限异常,进程当场崩溃。
内容的提问来源于stack exchange,提问作者Joseph Sible-Reinstate Monica
相关产品推荐
相关产品推荐

