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”,推测栈内存存储顺序异常。
问题原因
结构体的字段顺序与栈的实际存储顺序完全颠倒,导致寄存器结构体成员和栈中数据错位:
- 栈是向下增长的,宏中所有
push操作的内容会依次存放在CPU自动压栈内容的上方(栈顶方向),最终栈的布局为:段寄存器(gs→fs→es→ds)→通用寄存器(rax→rbx→...→r15)→中断号→错误码→CPU自动压入的iret相关值(rip→cs→rflags→rsp→ss)。 - 原结构体将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
相关产品推荐
相关产品推荐

