assembly 如何为中断服务例程保存x86_64上的寄存器?

quhf5bfb  于 2023-04-30  发布在  其他
关注(0)|答案(5)|浏览(150)

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

pusha
pushl   %ds
pushl   %es
pushl   %fs
pushl   %gs
pushl   %ss

pusha在64位模式下无效。那么,在64位模式下,在x86_64汇编中执行此操作的正确方法是什么呢?
pusha在64位模式下无效肯定是有原因的,所以我有一种感觉,手动推动所有寄存器可能不是一个好主意。

wi3ka0sx

wi3ka0sx1#

AMD在开发64位x86扩展时需要一些空间来为REX前缀和其他一些新指令添加新的操作码。他们改变了一些操作码的含义,这些新指令。
其中一些指令只是现有指令的简短形式,或者没有必要。PUSHA是受害者之一。目前还不清楚为什么他们禁止PUSHA,它似乎没有重叠任何新的指令操作码。也许它们保留了PUSHAPOPA操作码供将来使用,因为它们完全是冗余的,不会更快,也不会在代码中出现得足够频繁。
PUSHA的顺序是指令编码的顺序:eaxecxedxebxespebpesiedi。请注意,它冗余地推送了esp!你需要知道esp才能找到它推送的数据!
如果您正在从64位转换代码,PUSHA代码是不好的,无论如何,您需要更新它,以将新寄存器r8推送到r15。您还需要保存和恢复更大的SSE状态,xmm8xmm15。假设你要打败他们。
如果中断处理程序代码只是一个转发到C代码的存根,则不需要保存所有寄存器。您可以假设C编译器将生成保留rbxrbprsirdir12r15的代码。您应该只需要通过r11保存和恢复raxrcxrdxr8。* (注意:在Linux或其他System V ABI平台上,编译器将保留rbxrbpr12-r15,您可以预期rsirdi将被删除)*。
段寄存器在长模式下不保存任何值(如果中断的线程运行在32位兼容模式下,你必须保留段寄存器,谢谢ughoavgfhw)。实际上,他们去掉了长模式下的大部分分段,但FS仍然保留给操作系统,用作线程本地数据的基地址。寄存器值本身并不重要,FSGS的基址通过MSR 0xC00001000xC0000101设置。假设你不会使用FS,你不需要担心它,只要记住C代码访问的任何线程本地数据可以使用任何随机线程的TLS。要注意这一点,因为C运行库使用TLS来实现某些功能(例如:strtok通常使用TLS)。
将值加载到FSGS(即使在用户模式下)也会覆盖FSBASEGSBASE MSR。由于一些操作系统使用GS作为“处理器本地”存储(它们需要一种方法来为每个CPU提供指向结构的指针),因此它们需要将其保存在不会因在用户模式下加载GS而受到破坏的地方。为解决此问题,为GSBASE寄存器保留了两个MSR:一个活动的一个和一个隐藏的一个。在内核模式下,内核的GSBASE保存在通常的GSBASE MSR中,用户模式库保存在另一个(隐藏的)GSBASE MSR中。从内核模式切换到用户模式上下文时,以及保存用户模式上下文并进入内核模式时,上下文切换代码必须执行SWAPGS指令,该指令交换可见和隐藏GSBASE MSR的值。由于内核的GSBASE在用户模式下安全地隐藏在另一个MSR中,因此用户模式代码不能通过将值加载到GS中来破坏内核的GSBASE。当CPU重新进入内核模式时,上下文保存代码将执行SWAPGS并恢复内核的GSBASE

gblwokeq

gblwokeq2#

从现有的代码中学习这种事情。例如:

事实上,“手动推动”规则是AMD 64上的唯一方法,因为PUSHA不存在。AMD 64在这方面并不是唯一的--大多数非x86 CPU在某些时候也需要逐个寄存器的保存/恢复。
但如果仔细检查引用的源代码,您会发现并非所有中断处理程序都需要保存/恢复整个寄存器集,因此还有优化的空间。

yshpjwxd

yshpjwxd3#

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

sgtfey8w

sgtfey8w4#

你好,这可能不是正确的方法,但可以创建宏,如

.macro pushaq
    push %rax
    push %rcx
    push %rdx
    push %rbx
    push %rbp
    push %rsi
    push %rdi
.endm # pushaq

.macro popaq
    pop %rdi
    pop %rsi
    pop %rbp
    pop %rbx
    pop %rdx
    pop %rcx
    pop %rax
.endm # popaq

并且如果需要的话最终添加其它R8-15寄存器

smtd7mpg

smtd7mpg5#

举个例子,我今天测试了一个简短的程序,我想在开始做我们刚刚学到的系统调用之前,做同样的事情,备份所有的寄存器。因此,我首先尝试了pusha和popa,我在旧的IA-32英特尔架构软件开发人员手册中找到的东西。然而,它没有工作。我已经手动测试过了,但是效果很好。

#Author: Jonathan Lee
#Professor: Devin Cook
#CSC35
#4-27-23
#practicesyscall.asm

.intel_syntax noprefix

.data

Message:
        .asciz "Learning about system calls.\n"

.text

.global _start

_start:
        pusha   #facilitates saving the current general purpose registers only 32 bit processor
        mov rax, 1
        mov rdi, 1
        lea rsi, Message
        mov rdx, 30
        syscall
        popa    #facilitates restoring the registers only 32 bit processor
        mov rax, 60
        mov rdi, 0
        syscall

这是使用x64编译时的结果:

practicesyscall.asm: Assembler messages:
practicesyscall.asm:19: Error: `pusha' is not supported in 64-bit mode
practicesyscall.asm:25: Error: `popa' is not supported in 64-bit mode

如果没有pusha和popa助记符,它就可以工作了,结果如下:

Learning about system calls.

这将适用于x32模式:Intel Ref Document
但是,如果你想尝试这个方法,它确实可以手动工作:

#Jonathan Lee
#CSC35
#Professor Cook
#3-31-23
#Practice NON credit assignment
.intel_syntax noprefix
.data

Intro:
        .ascii "Hello world my name is Jonathan Lee\n\n                                     SAC STATE STINGERS UP\n\n"
        .ascii "This program is to aid in learning more about stacks inside of assembly language.\n"
        .ascii "Register RSP is used to point to the top of the stack. If you use (push) it will load the stack and move the pointer (RSP) automatically.\n"
        .ascii "The stack counts down not up in assembly language.\n"
        .ascii "This is the current RSP point prior to loading a stack or program run:--------------------->  \0"

NewLine:
        .ascii "\n\0"

StackLoad:
    .ascii "Program will now load 1-14 into registers and push to the stack after it will write the register values and current RSP pointer value again.\n"
        .ascii "This will not use registers RSP/RDI they are in use for stack pointer and class subroutines.\n\0"

ZeroLoad:
    .ascii "Program will now load 0 into all registers and write register values after to show values are now stored in memory not registers.\n\0"

PopLoad:
        .ascii "Note RSP is still using the same value. The program will now pop the stack and reverse load the values into the registers, after will write register values.\n"
        .ascii "Last In First Out.\n\0"

RSPAddress:
    .ascii "This is the current next available RSP Pointer Memory Address on top of stack:------------->  \0"

PostStack:
    .ascii "This is the the current next available RSP Pointer Memory Address after stack is popped:--->  \0"

PreExit:
        .ascii "\nNote that the RSP is now pointing to the same memory address value as when the program started.\n\n\0"

.text
.global _start

_start:
        call ClearScreen
        lea rdi, NewLine
        call WriteString
        lea rdi, Intro
        call WriteString
        mov rdi, rsp
        call WriteHex
        lea rdi, NewLine
        call WriteString

        mov rax, 1
        mov rbx, 2
        mov rcx, 3
        mov rdx, 4
        mov rsi, 5
        mov rdi, 6
        mov rbp, 7
        #rsp index use
        mov r8, 8
        mov r9, 9
        mov r10, 10
        mov r11, 11
        mov r12, 12
        mov r13, 13
        mov r14, 14
        mov r15, 15

        lea rdi, StackLoad
        call WriteString
        push rax
        push rbx
        push rcx
        push rdx
        push rsi
        push rbp
        push r8
        push r9
        push r10
        push r11
        push r12
        push r13
        push r14
        push r15

        call WriteRegisters
        lea rdi, RSPAddress
        call WriteString
        mov rdi, rsp
        call WriteHex
        lea rdi, NewLine
        call WriteString
        lea rdi, ZeroLoad
        call WriteString

        mov rax, 0
        mov rbx, 0
        mov rcx, 0
        mov rdx, 0
        mov rsi, 0
        mov rbp, 0
        mov r8, 0
        mov r9, 0
        mov r10, 0
        mov r11, 0
        mov r12, 0
        mov r13, 0
        mov r14, 0
        mov r15, 0

        call WriteRegisterslea rdi, RSPAddress
        call WriteString
        mov rdi, rsp
        call WriteHex
        lea rdi, NewLine
        call WriteString

        lea rdi, PopLoad
        call WriteString

        pop rax
        pop rbx #Last In First Out
        pop rcx
        pop rdx
        pop rsi
        pop rbp
        pop r8
        pop r9
        pop r10
        pop r11
        pop r12
        pop r13
        pop r14#flip stack
        pop r15

        call WriteRegisters
        lea rdi, PostStack
        call WriteString
        mov rdi, rsp
        call WriteHex
        lea rdi, NewLine
        call WriteString
        lea rdi, PreExit
        call WriteString

        call Exit

结果就是:

Hello world my name is Jonathan Lee

                                     SAC STATE STINGERS UP

This program is to aid in learning more about stacks inside of assembly language.
Register RSP is used to point to the top of the stack. If you use (push) it will load the stack and move the pointer (RSP) automatically.
The stack counts down not up in assembly language.
This is the current RSP point prior to loading a stack or program run:--------------------->  00007FFEC3675420
Program will now load 1-14 into registers and push to the stack after it will write the register values and current RSP pointer value again.
This will not use registers RSP/RDI they are in use for stack pointer and class subroutines.
 
RAX : 0000000000000001    R8  : 0000000000000008 
RBX : 0000000000000002    R9  : 0000000000000009 
RCX : 0000000000000003    R10 : 000000000000000A 
RDX : 0000000000000004    R11 : 000000000000000B 
RDI : 0000000000600AA5    R12 : 000000000000000C 
RSI : 0000000000000005    R13 : 000000000000000D 
RBP : 0000000000000007    R14 : 000000000000000E 
RSP : 00007FFEC36753A0    R15 : 000000000000000F

This is the current next available RSP Pointer Memory Address on top of stack:------------->  00007FFEC36753B0
Program will now load 0 into all registers and write register values after to show values are now stored in memory not registers.
 
RAX : 0000000000000000    R8  : 0000000000000000 
RBX : 0000000000000000    R9  : 0000000000000000 
RCX : 0000000000000000    R10 : 0000000000000000 
RDX : 0000000000000000    R11 : 0000000000000000 
RDI : 0000000000600B90    R12 : 0000000000000000 
RSI : 0000000000000000    R13 : 0000000000000000 
RBP : 0000000000000000    R14 : 0000000000000000 
RSP : 00007FFEC36753A0    R15 : 0000000000000000

This is the current next available RSP Pointer Memory Address on top of stack:------------->  00007FFEC36753B0
Note RSP is still using the same value. The program will now pop the stack and reverse load the values into the registers, after will write register values.
Last In First Out.
 
RAX : 000000000000000F    R8  : 0000000000000009 
RBX : 000000000000000E    R9  : 0000000000000008 
RCX : 000000000000000D    R10 : 0000000000000007 
RDX : 000000000000000C    R11 : 0000000000000005 
RDI : 0000000000600C13    R12 : 0000000000000004 
RSI : 000000000000000B    R13 : 0000000000000003 
RBP : 000000000000000A    R14 : 0000000000000002 
RSP : 00007FFEC3675410    R15 : 0000000000000001

This is the the current next available RSP Pointer Memory Address after stack is popped:--->  00007FFEC3675420

Note that the RSP is now pointing to the same memory address value as when the program started.

长话短说,您可以手动将寄存器一个接一个地加载到堆栈中,并在需要时将其弹出以恢复堆栈。

相关问题