如何在x86_64上保存寄存器以获取中断服务程序?

Mr.*_*nce 24 assembly x86-64 isr

我正在看一个学校项目中的一些旧代码,并试图在我的笔记本电脑上编译它时遇到了一些问题.它最初是为旧的32位版本的gcc编写的.无论如何,我试图将一些程序集转换为64位兼容代码并遇到一些障碍.

这是原始代码:

pusha
pushl   %ds
pushl   %es
pushl   %fs
pushl   %gs
pushl   %ss
Run Code Online (Sandbox Code Playgroud)

pusha在64位模式下无效.那么在64位模式下,在x86_64汇编中执行此操作的正确方法是什么?

必须有一个原因,为什么pusha在64位模式下无效,所以我有一种感觉手动推送所有寄存器可能不是一个好主意.

dou*_*536 18

REX在开发64位x86扩展时,AMD需要一些空间来添加前缀的新操作码和一些其他新指令.他们将某些操作码的含义改为新指令.

其中一些说明只是现有说明的简写形式,或者不是必需的.PUSHA是受害者之一.目前尚不清楚他们为什么禁止PUSHA,但似乎并没有重叠任何新的指令操作码.也许它们是未来使用的保留PUSHAPOPA操作码,因为它们完全是冗余的,并且不会更快,并且在代码中不会频繁发生.

的顺序PUSHA是指令编码的顺序:eax,ecx,edx,ebx,esp,ebp,esi,edi.注意它被多余推esp!你需要知道esp找到它推送的数据!

如果是从64位代码转换的PUSHA代码是没有好,无论如何,你需要更新它推新寄存器r8r15.您还需要保存和恢复一个更大的SSE状态,xmm8直通xmm15.假设你要破坏它们.

如果中断处理程序代码只是转发到C代码的存根,则不需要保存所有寄存器.你可以假设C编译器将生成将保持代码rbx,rbp,rsi,rdi,和r12直通r15.您应该只需要保存和恢复rax,rcx,rdxr8直通r11.(注:在Linux或其它系统V ABI的平台,编译器会保留rbx,rbp,r12- r15,你可以期望rsirdi惨败).

段寄存器在长模式下不保持任何值(如果被中断的线程在32位兼容模式下运行,则必须保留段寄存器,谢谢ughoavgfhw).实际上,他们在长模式下摆脱了大部分细分,但FS仍然保留给操作系统用作线程本地数据的基地址.寄存器值本身并不重要,它的基础FSGS设置通过MSR 0xC00001000xC0000101.假设你不会使用FS你不需要担心它,只要记住C代码访问的任何线程本地数据都可以使用任何随机线程的TLS.请注意这一点,因为C运行时库使用TLS来实现某些功能(例如:strtok通常使用TLS).

将值加载到FSGS(甚至在用户模式下)将覆盖FSBASEGSBASEMSR.由于某些操作系统使用GS"处理器本地"存储(它们需要一种方法来指向每个CPU的结构),因此需要将它保留在不会因GS用户模式加载而被破坏的地方.为了解决这个问题,为GSBASE寄存器保留了两个MSR :一个是活动的,一个是隐藏的.在内核模式下,内核GSBASE保存在通常的GSBASEMSR中,而用户模式库则保存在另一个(隐藏的)GSBASEMSR中.当上下文从内核模式切换到用户模式上下文时,以及在保存用户模式上下文和进入内核模式时,上下文切换代码必须执行SWAPGS指令,该指令交换可见和隐藏GSBASEMSR 的值.由于内核GSBASE在用户模式下安全地隐藏在其他MSR中,因此用户模式代码无法GSBASE通过加载值来破坏内核GS.当CPU重新进入内核模式时,上下文保存代码将执行SWAPGS并恢复内核GSBASE.

  • 我猜 AMD 不想为 64 位“pusha”编写新的微代码,因为它可以保存两倍的寄存器,每个寄存器的宽度都是两倍。但有趣的是,AMD k7 和 k8 在 32 位模式下的“pusha”速度是 8 个背靠背“push”指令的两倍(根据 Agner Fog 的测试,每 4 个时钟吞吐量一个指令:https://agner.org /optimize/),因此显然它一次存储成对的 32 位寄存器,以绕过每个时钟 1 次存储的瓶颈。在 Intel 上,它不值得用于性能,只是代码大小(8c 吞吐量,Merom 上 18 uops,Intel 的第一个 64 位 P6 uarch。) (2认同)

Fra*_*kH. 8

学习做这种事情的现有代码.例如:

实际上,"手动推送"regs是AMD64的唯一途径,因为PUSHA那里不存在.AMD64在这方面并不是唯一的 - 大多数非x86 CPU确实需要逐个寄存器保存/恢复.

但是,如果仔细检查引用的源代码,您会发现并非所有中断处理程序都需要保存/恢复整个寄存器集,因此存在优化空间.


use*_*783 7

pusha在64位模式下无效,因为它是冗余的.单独推送每个寄存器正是要做的事情.

  • 此外,您不能在64位模式下推送段寄存器.您需要先将它们复制到另一个寄存器.`mov%ds,%eax; 推%rax`. (4认同)
  • @doug65536有时仍然有必要保留它们。例如,运行 32 位程序的 64 位操作系统需要在收到中断时保存段寄存器,因为该程序使用了分段。 (2认同)

baz*_*baz 5

嗨,这可能不是正确的方法,但是可以创建像这样的宏

.macro pushaq
    push %rax
    push %rcx
    push %rdx
    push %rbx
    push %rbp
    push %rsi
    push %rdi
.endm # pushaq
Run Code Online (Sandbox Code Playgroud)

.macro popaq
    pop %rdi    
    pop %rsi    
    pop %rbp    
    pop %rbx    
    pop %rdx    
    pop %rcx
    pop %rax
.endm # popaq
Run Code Online (Sandbox Code Playgroud)

如果需要,最终添加其他 r8-15 寄存器