You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

x86_64架构下ISR宏中断号错误的排查与解决

问题描述

在x86_64架构CPU中,触发中断时CPU会自动按如下顺序将iret相关寄存器压入栈(栈顶到栈底):

+-------------------------+
| err. code(if applicable)|
+-------------------------+
| rip                     |
+-------------------------+
| cs                      |
+-------------------------+
| rflags                  |
+-------------------------+
| rsp                     |
+-------------------------+
| ss                      |
+-------------------------+

定义了如下CPU状态结构体:

struct registers { // Total 26*8 = 208 bytes which is 16 bytes aligned
    // CPU state
    uint64_t iret_rip;      // Offset 8*0
    uint64_t iret_cs;       // Offset 8*1
    uint64_t iret_rflags;   // Offset 8*2
    uint64_t iret_rsp;      // Offset 8*3
    uint64_t iret_ss;       // Offset 8*4

    uint64_t err_code;      // Offset 8*5
    uint64_t int_no;        // Offset 8*6

    // General purpose registers
    uint64_t r15;           // Offset 8*7
    uint64_t r14;           // Offset 8*8
    uint64_t r13;           // Offset 8*9
    uint64_t r12;           // Offset 8*10
    uint64_t r11;           // Offset 8*11
    uint64_t r10;           // Offset 8*12
    uint64_t r9;            // Offset 8*13
    uint64_t r8;            // Offset 8*14
    uint64_t rsi;           // Offset 8*15
    uint64_t rdi;           // Offset 8*16
    uint64_t rbp;           // Offset 8*17
    uint64_t rdx;           // Offset 8*18
    uint64_t rcx;           // Offset 8*19
    uint64_t rbx;           // Offset 8*20
    uint64_t rax;           // Offset 8*21

    // Segment registers
    uint64_t ds;            // Offset 8*22
    uint64_t es;            // Offset 8*23
    uint64_t fs;            // Offset 8*24
    uint64_t gs;            // Offset 8*25
} __attribute__((packed));
typedef struct registers registers_t;

通过void isr_handler(registers_t *regs)处理中断,编写了ISR_NOERRCODE宏(无错误码中断时手动压入虚拟错误码),代码如下:

%macro ISR_NOERRCODE 1
    [global isr%1]
    isr%1:
        cli;

        push 0          ; Dummy error code
        push %1         ; Interrupt number
        
        push r15        ; Save general-purpose registers in reverse order (to match RESTORE_REGISTERS)
        push r14
        push r13
        push r12
        push r11
        push r10
        push r9
        push r8
        push rsi
        push rdi
        push rbp
        push rdx
        push rcx
        push rbx
        push rax
        mov ax, ds      ; Save segment registers
        push rax
        mov ax, es
        push rax
        push fs
        push gs
        
        mov rdi, rsp         ; Pass pointer to the `registers_t` structure
        cld                  ; Clear the direction flag
        call isr_handler     ; Call the interrupt handler

        pop gs              ; Restore segment registers
        pop fs
        pop rax
        mov es, ax
        pop rax
        mov ds, ax
        pop rax             ; Restore general-purpose registers
        pop rbx
        pop rcx
        pop rdx
        pop rbp
        pop rdi
        pop rsi
        pop r8
        pop r9
        pop r10
        pop r11
        pop r12
        pop r13
        pop r14
        pop r15
        add rsp, 16         ; Clean up interrupt no and dummy error code

        iretq               ; Return from the interrupt using IRETQ (iret values remain intact)
%endmacro

测试执行asm volatile ("int $0x3");时,预期显示“收到中断3,错误码0”,实际显示“收到中断0,错误码0”,推测栈内存存储顺序异常。

问题原因

结构体的字段顺序与栈的实际存储顺序完全颠倒,导致寄存器结构体成员和栈中数据错位:

  1. 栈是向下增长的,宏中所有push操作的内容会依次存放在CPU自动压栈内容的上方(栈顶方向),最终栈的布局为:段寄存器(gs→fs→es→ds)→通用寄存器(rax→rbx→...→r15)→中断号→错误码→CPU自动压入的iret相关值(rip→cs→rflags→rsp→ss)。
  2. 原结构体将iret相关值放在最前面(低偏移),而宏中把栈顶地址(指向gs)作为结构体指针传入,导致结构体的int_no字段读取的是栈中靠前的无关数据(而非手动压入的中断号),最终出现错误的中断号显示。
解决方法

调整结构体的字段顺序,使其与栈的实际存储顺序(从栈顶到栈底)完全一致:

struct registers { // Total 26*8 = 208 bytes which is 16 bytes aligned
    // Segment registers (栈顶起始)
    uint64_t gs;            // Offset 0
    uint64_t fs;            // Offset 8
    uint64_t es;            // Offset 16
    uint64_t ds;            // Offset 24

    // General purpose registers
    uint64_t rax;           // Offset 32
    uint64_t rbx;           // Offset 40
    uint64_t rcx;           // Offset 48
    uint64_t rdx;           // Offset 56
    uint64_t rbp;           // Offset 64
    uint64_t rdi;           // Offset 72
    uint64_t rsi;           // Offset 80
    uint64_t r8;            // Offset 88
    uint64_t r9;            // Offset 96
    uint64_t r10;           // Offset 104
    uint64_t r11;           // Offset 112
    uint64_t r12;           // Offset 120
    uint64_t r13;           // Offset 128
    uint64_t r14;           // Offset 136
    uint64_t r15;           // Offset 144

    // Interrupt info
    uint64_t int_no;        // Offset 152
    uint64_t err_code;      // Offset 160

    // CPU IRET state (栈底部分)
    uint64_t iret_rip;      // Offset 168
    uint64_t iret_cs;       // Offset 176
    uint64_t iret_rflags;   // Offset 184
    uint64_t iret_rsp;      // Offset 192
    uint64_t iret_ss;       // Offset 200
} __attribute__((packed));
typedef struct registers registers_t;

调整后,结构体的每个字段会精准对应栈中的数据:

  • 栈顶的gs对应结构体的gs字段
  • 手动压入的中断号对应结构体的int_no字段
  • 手动压入的虚拟错误码对应结构体的err_code字段
  • CPU自动压入的iret相关值对应结构体末尾的iret_*字段

内容的提问来源于stack exchange,提问作者baponkar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 17:54:50