Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/css/33.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Assembly 为什么我';我不遵循在异常处理程序中保存寄存器的约定吗?_Assembly_X86 64_Cpu Registers_Interrupt Handling - Fatal编程技术网

Assembly 为什么我';我不遵循在异常处理程序中保存寄存器的约定吗?

Assembly 为什么我';我不遵循在异常处理程序中保存寄存器的约定吗?,assembly,x86-64,cpu-registers,interrupt-handling,Assembly,X86 64,Cpu Registers,Interrupt Handling,在我的文章中,我在汇编中发布了此代码(x86-64 att),它替换了无效操作码的处理程序(如果what\u to\u do函数返回0,则可能调用上一个处理程序): 你们中的许多人指出,我没有遵循关于保存r8和rdx的指导原则,但我就是不明白为什么 但是: 这些寄存器是caller save,我在调用function\u call并再次加载它们之前保存它们,那么这有什么问题 我不需要在调用jmp之前保存它们,这不是函数调用 另外,我应该如何在不破坏整个代码的情况下修复它 第一次编辑: .g

在我的文章中,我在汇编中发布了此代码(x86-64 att),它替换了无效操作码的处理程序(如果
what\u to\u do
函数返回0,则可能调用上一个处理程序):

你们中的许多人指出,我没有遵循关于保存r8和rdx的指导原则,但我就是不明白为什么

但是:

  • 这些寄存器是caller save,我在调用
    function\u call
    并再次加载它们之前保存它们,那么这有什么问题

  • 我不需要在调用jmp之前保存它们,这不是函数调用

  • 另外,我应该如何在不破坏整个代码的情况下修复它


    第一次编辑:

    .globl my_ili_handler
    
    .text
    .align 4, 0x90
    
    my_ili_handler:
    
        movq (%rsp), %r8 # loading %rip from stack
        movb (%r8), %dil # reading first byte in the invalid opcode
        cmpb $0x0F, %dil
        jne function_call
        movb 1(%r8), %dil # else read the 2nd byte instead
        addq $1, %r8
        
    function_call:
        addq $1, %r8
        pushq %rbp # save old %rbp
        movq %rsp, %rbp # move %rbp to top
        # %rax, %rdi, %rsi, %rdx, %rcx, %r8, %r9, %r10, %r11 caller saved.
        subq $72, %rsp
        
        # backup all caller-saved registers
        pushq %rax
        pushq %rdi
        pushq %rsi
        pushq %rdx
        pushq %rcx
        pushq %r8
        pushq %r9
        pushq %r10
        pushq %r11
        
        call what_to_do # unsigned int what_to_do(unsigned char magic)
        
        # restore all caller-saved registers
        popq %r11
        popq %r10
        popq %r9
        popq %r8
        popq %rcx
        popq %rdx
        popq %rsi
        popq %rdi
        popq %rax
        
        leave # (mov %rbp, %rsp) & (pop %rbp) 
        
        cmpl $0, %eax
        je old_handler
        mov %eax, %edi # zero the upper part of %rdi
        addq $8, %rsp # pop old %rip from stack
        pushq %r8
        jmp end
    
    old_handler:
        jmp *old_ili_handler(%rip)
    
    end:
        iretq # go back to user space
    

    您的中断处理程序不是一个函数。寄存器的整个传入状态(RSP和RFLAG除外)属于用户空间

    jmp*旧的ili处理程序(%rip)
    最终将在故障发生时记录用户空间的状态,因此您希望避免扭曲信号处理程序或核心转储看到的用户空间状态

    您可以看到,所有寄存器都是旧处理程序尾部调用的“参数”。(它也是一个中断处理程序,而不是函数,因此您可以
    jmp
    将堆栈/寄存器置于与处理程序入口状态相匹配的状态,因此它的工作方式就像是直接从用户空间中的错误调用它一样。)

    请注意类似于
    intfoo(intx){returnbar(x);}
    这样的函数,它将编译为
    jmp bar
    ,而不是
    callbar
    /
    ret
    。i、 e.一个优化的tailcall,只在寄存器中留下arg。但是,对于可以返回、传递信号或触发核心转储的异常处理程序,所有寄存器中的整个用户空间状态实际上是一个参数


    一般来说,对于其他故障,如页面故障,在修复问题后可以恢复用户空间,更重要的是不要损坏寄存器:不要只是将错误信息放入核心转储(或通过SIGILL处理程序中断偶尔模拟丢失指令的程序),而是中断执行
    添加(%r8)的代码,%edi
    如果最终返回到用户空间的寄存器值不同。事实上,您的代码现在有时会跳转到
    iret
    ,因此您直接返回用户空间重试错误指令,可能是在修复它之后,所以您确实遇到了这个问题

    请注意,您实际上应该保存/恢复
    调用what_to_do
    周围所有调用失败的寄存器,因为它是一个遵循C调用约定的函数


    e、 g.安全代码可能如下所示。(未经测试)。将RIP传递给
    what\u to\u do
    并让它返回新的RIP,或者
    0
    运行旧的处理程序,可能更有意义。(作为奖励,您不需要在该函数调用中保存任何额外的状态,只需保存用户空间状态。)

    x86指令在操作码之后有一个可变的字节数,这取决于寻址模式和立即数,因此仅将用户空间RIP增加1或2是没有意义的。或者如果第一个字节实际上是前缀,如
    rep
    rex

    您可能有长度超过2字节的非法指令,如使用寄存器源编码的
    lea
    ,例如(REX+opcode+modrm)。或者a
    66 0F 0B
    (UD2前面有两个前缀)。因此,当函数只看1个字节时,可能会混淆它

    但无论如何,我保留了原始指令长度解码,以显示使用调用保留寄存器在整个调用中记住某些内容,而不是保存用户空间的状态

    .globl my_ili_handler
    .text
    .p2align 4
    my_ili_handler:
        push   %rbx    # save a call-preserved reg for our own use
    
        # %rax, %rdi, %rsi, %rdx, %rcx, %r8, %r9, %r10, %r11 are call-clobbered
        push   %rax
        push   %rcx
        push   %rdx
        push   %rsi
        push   %rdi
        push   %r8
        push   %r9
        push   %r10
        push   %r11
    
        mov   10*8(%rsp), %rbx   # loading user-space fault address from exception frame
                              # note the n*8(%rsp) since this is after n pushes; same address as (%rsp) on entry.
    
        movzbl (%rbx), %edi   # byte load of the invalid opcode
        inc    %rbx
        cmp    $0x0F, %edi    # check for 2-byte opcode escape byte
        jne function_call
        inc    %rbx
        movzbl (%rbx), %edi   # else read the 2nd byte instead
        
    function_call:
       # RBX points to fault-address + 1 or 2 depending on seeing 0F.
       # Very primitive instruction-length decoding that ignores prefixes
       # and illegal forms of longer instructions with ModRM and/or immediate operands
    
        # subq $8, %rsp        # 16-byte stack alignment probably not needed in kernel, and I didn't check what the initial alignment was on entry vs. the number of pushes
        cld                    # C calling convention requires DF=0, user-space might have left DF=1
           # 64-bit mode can I think avoid worrying about DS and ES settings
        call what_to_do        # unsigned int what_to_do(unsigned char magic)
        
        cmpl $0, %eax
    # now restore everything, before we either 
    # run the old handler transparently  or  return to user-space with its regs unchanged
    
        pop   %r11
        pop   %r10
        pop   %r9
        pop   %r8
    
        pop   %rdi
        pop   %rsi
        pop   %rdx
        pop   %rcx
        pop   %rax
    
        je  run_old_handler
    end:
        # mov %eax, %edi        # zero the upper part of %rdi.
             #  IDK what this was for.  Is user-space supposed to get this return value?
             # If so, only restore RAX in the other path instead of before the branch
             # and   add $8, %rsp   here instead.
    
        mov   %rbx, 8(%rsp)     # set the user-space RIP
        pop   %rbx              # restore our call-preserved register
        iretq                   # and return to user-space at the updated RIP
        
    run_old_handler:
        pop   %rbx             # just restore RBX
        jmp *old_ili_handler(%rip)   # and run the old handler with all registers in identical state to entry to this handler.
    

    如果标签中的“function”一词没有拼写错误,那么您的代码读起来会更好,尤其是因为您现在在另一条指令中引用了标签名称:/您正在编写一个异常处理程序,而不是一个普通函数!将控制权转移给您的代码并不是自动呼叫您的,也不希望任何寄存器被修改。所以您必须保留所有寄存器、标志和其他相关CPU状态,包括在正常函数调用中“调用方保存”的状态。正常的呼叫约定不适用,因为“呼叫者”不与它们合作。但这不适用于我应该何时备份它们,何时恢复它们,以及我应该每隔一个寄存器备份一次吗?(我知道cmp影响一个寄存器,我是否也应该备份它?)您的编辑使问题中的代码变得毫无意义(因为没有设置R8),并使我的答案无效,因为您不再调用C函数。滚回去。我不明白如何将这种更改描述为“修复”代码,因为它仍然修改R8而不保存它。如果你想写一个答案,把它作为一个答案,而不是对问题的编辑。我还不明白。。。我已经保存/恢复了所有关于call what_to_do的调用中断寄存器。那么问题又是什么呢?@john:如果中断服务路由(例如网卡)在每次运行时都将RAX设置为123,则会发生什么情况?在中断的任意两条指令之间异步运行?显然很糟糕,对吧?这就是您的异常处理程序正在执行的操作,除非只有在被非法指令调用时才执行。@john:另外,保存第一个参数的是RDI,而不是RDX。您正在加载到DIL,而不是DL。因此,根据您所说的,我应该在调用what-to-what-to-what-what-what-what-to-what-what-what-what-what-what-to-what-what-what-what-to-what-what-to-what-to-what-to?我听上去不对……哇,我真佩服你的耐心,彼得!:)
    .globl my_ili_handler
    .text
    .p2align 4
    my_ili_handler:
        push   %rbx    # save a call-preserved reg for our own use
    
        # %rax, %rdi, %rsi, %rdx, %rcx, %r8, %r9, %r10, %r11 are call-clobbered
        push   %rax
        push   %rcx
        push   %rdx
        push   %rsi
        push   %rdi
        push   %r8
        push   %r9
        push   %r10
        push   %r11
    
        mov   10*8(%rsp), %rbx   # loading user-space fault address from exception frame
                              # note the n*8(%rsp) since this is after n pushes; same address as (%rsp) on entry.
    
        movzbl (%rbx), %edi   # byte load of the invalid opcode
        inc    %rbx
        cmp    $0x0F, %edi    # check for 2-byte opcode escape byte
        jne function_call
        inc    %rbx
        movzbl (%rbx), %edi   # else read the 2nd byte instead
        
    function_call:
       # RBX points to fault-address + 1 or 2 depending on seeing 0F.
       # Very primitive instruction-length decoding that ignores prefixes
       # and illegal forms of longer instructions with ModRM and/or immediate operands
    
        # subq $8, %rsp        # 16-byte stack alignment probably not needed in kernel, and I didn't check what the initial alignment was on entry vs. the number of pushes
        cld                    # C calling convention requires DF=0, user-space might have left DF=1
           # 64-bit mode can I think avoid worrying about DS and ES settings
        call what_to_do        # unsigned int what_to_do(unsigned char magic)
        
        cmpl $0, %eax
    # now restore everything, before we either 
    # run the old handler transparently  or  return to user-space with its regs unchanged
    
        pop   %r11
        pop   %r10
        pop   %r9
        pop   %r8
    
        pop   %rdi
        pop   %rsi
        pop   %rdx
        pop   %rcx
        pop   %rax
    
        je  run_old_handler
    end:
        # mov %eax, %edi        # zero the upper part of %rdi.
             #  IDK what this was for.  Is user-space supposed to get this return value?
             # If so, only restore RAX in the other path instead of before the branch
             # and   add $8, %rsp   here instead.
    
        mov   %rbx, 8(%rsp)     # set the user-space RIP
        pop   %rbx              # restore our call-preserved register
        iretq                   # and return to user-space at the updated RIP
        
    run_old_handler:
        pop   %rbx             # just restore RBX
        jmp *old_ili_handler(%rip)   # and run the old handler with all registers in identical state to entry to this handler.