Search code examples
assemblyx86x86-64interruptosdev

How does stack memory will be use to store cpu state during interrupt?


When interrupt will happen in x86 architecture based 64 bit cpu the cpu will push automatically some iret registers values following way into the stack

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

Now I have defined cpu state structure by follwoing way

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;

A void isr_handler(registers_t *regs) function will manage interrupt service routine. To create above structure in stack by proper order along with 16 byte align and then call isr_handler function and struction_start pointer placed into rdi so I have written the below macro when cpu do not push a error code but I push a dummy error code.

%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

Bu unfortunately this macro is not working when I am trying to test interrupt by asm volatile ("int $0x3"); which should print received interrupt 3 with error code 0 but showing received interrupt 0 with error code 0. This is showing stack memory may not properly store values. What is the reason of wrong interrupt code?

Full code link.

How can I resolve this issue as it is not showing proper interrupt code.


Solution

  • Remember that the stack on x86 grows downward, so the most recently pushed element is at the lowest address. But in a C struct, the first member declared is at the lowest address. So you have them reversed.

    So to be consistent with your asm code, the declaration of struct registers should look like:

    struct registers {
        uint64_t gs;
        uint64_t fs;
        uint64_t es;
        uint64_t ds;
        uint64_t rax;
        uint64_t rbx;
        // ... all GP registers in reverse order from how you pushed them
        uint64_t r14;
        uint64_t r15;
        uint64_t int_no;
        uint64_t err_code;
        uint64_t iret_rip;
        uint64_t iret_cs;
        uint64_t iret_rflags;
        uint64_t iret_rsp;
        uint64_t iret_ss;
    };
    

    By the way, __attribute__((packed)) is unnecessary here, since all the members are the same size (8 bytes) and the struct should always be aligned to 8 bytes. The compiler will not insert padding in such a struct; the ABI forbids it.

    Another note is that your stack diagram is laid out with the lowest address at the top, and increasing addresses as you move down the page. This choice could lead to confusion, as Intel's manuals follow the opposite convention.