Why am I getting error: interrupt service routine should have ‘unsigned long int’ as the second argument?

Viewed 430

I'm getting a simple error because I read a document (https://gcc.gnu.org/onlinedocs/gcc/x86-Function-Attributes.html#x86-Function-Attributes) and wanted to do things properly. In this document is stated that:

An interrupt handler must be declared with a mandatory pointer argument:

struct interrupt_frame;

__attribute__ ((interrupt))
void
f (struct interrupt_frame *frame)
{
}

and you must define struct interrupt_frame as described in the processor’s manual.

Exception handlers differ from interrupt handlers because the system pushes an error code on the stack. An exception handler declaration is similar to that for an interrupt handler, but with a different mandatory function signature. The compiler arranges to pop the error code off the stack before the IRET instruction.

#ifdef __x86_64__
typedef unsigned long long int uword_t;
#else
typedef unsigned int uword_t;
#endif

struct interrupt_frame;

__attribute__ ((interrupt))
void
f (struct interrupt_frame *frame, uword_t error_code)
{
 ...
}

Exception handlers should only be used for exceptions that push an error code; you should use an interrupt handler in other cases. The system will crash if the wrong kind of handler is used.

Exceptions that push an error code should thus have a 64 bits long as the second argument. Others should have only the user defined interrupt_frame struct. I thus attempted to do that with the following code:

struct InterruptFrame{
    UINT64 rsp;
};

//Divide by zero error
__attribute__((interrupt)) void IDT::isr0(InterruptFrame* frame) {
    asm volatile("hlt");
}

//Page fault
__attribute__((interrupt)) void IDT::isr14(InterruptFrame* frame, unsigned long long int errorCode) {
    asm volatile("hlt");
}

Those are just examples because I have more separate ISRs. When I compile with

g++ -static -ffreestanding -nostdlib -mgeneral-regs-only -mno-red-zone -c -m64 Kernel/Source/IDT.cpp -oKernel/Object/IDT.o

I get an error stating

error: interrupt service routine should have ‘unsigned long int’ as the second argument

even though I did exactly as mentioned in documentation. I get the error for each ISR even for the page fault handler which currently has an unsigned long long int as a second argument. I didn't find much searching on the web for the error so I thought of asking here.

What is wrong? Also, what members should the InterruptFrame struct contain for x86-64?

2 Answers

What is wrong?

I think the error message seems fairly clear:

error: interrupt service routine should have ‘unsigned long int’ as the second argument

Based on that error message, I recommend solving the problem by having ‘unsigned long int’ as the second argument instead of unsigned long long int.

The source code of the error message reveals the conditions for which type to use:

gcc/config/i386/i386-options.c

  error ("interrupt service routine should have %qs "
     "as the second argument",
     TARGET_64BIT
     ? (TARGET_X32 ? "unsigned long long int"
           : "unsigned long int")
     : "unsigned int");

So, it seems that unsigned int should be used when targeting 32 bit x86, unsigned long long should be used when targeting the 32 bit ABI of 64 x86_64 and unsigned long when targeting 64 bit ABI of x86_64.


Also, what members should the InterruptFrame struct contain for x86-64?

I don't think you necessarily need to define the struct until you intend to access it. But in case you do, the AMD64 Architecture Programmer’s Manual Volume 2: System Programming says:

8.9 Long-Mode Interrupt Control Transfers

8.9.3 Interrupt Stack Frame

Interrupt-Handler Stack

Above that, there is detailed description about the contents.

Based on the diagram, following seems to match (Untested. I recommend reader to verify whether the order is correct or reversed):

struct interrupt_frame
{
    std::uint64_t instruction_pointer;
    std::uint64_t code_segment;
    std::uint64_t rflags;
    std::uint64_t register_stack_pointer;
    std::uint64_t stack_segment;
}

In the end, the problem was a minor mistake of mine. I declared the __attribute__((interrupt)) on the declaration of the C++ IDT member functions instead of on the prototypes. You can reproduce the issue with the following:

struct InterruptFrame{
    unsigned long rsp;
};

class IDT{
public:
    static void isr0(InterruptFrame* frame);
    static void isr14(InterruptFrame* frame, unsigned long errorCode);
};

__attribute__((interrupt)) void IDT::isr0(InterruptFrame* frame) {
    asm volatile("hlt");
}

__attribute__((interrupt)) void IDT::isr14(InterruptFrame* frame, unsigned long errorCode) {
    asm volatile("hlt");
}

This file produces an error. Whereas the following file doesn't produce any error.

struct InterruptFrame{
    unsigned long rsp;
};

class IDT{
public:
    __attribute__((interrupt)) static void isr0(InterruptFrame* frame);
    __attribute__((interrupt)) static void isr14(InterruptFrame* frame, unsigned long errorCode);
};

void IDT::isr0(InterruptFrame* frame) {
    asm volatile("hlt");
}

void IDT::isr14(InterruptFrame* frame, unsigned long errorCode) {
    asm volatile("hlt");
}

I can disassemble the code and see that the interrupt code seems correct:

user@user-System-Product-Name:~$ objdump -d test.o

test.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <_ZN3IDT4isr0EP14InterruptFrame>:
   0:   f3 0f 1e fa             endbr64 
   4:   55                      push   %rbp
   5:   48 89 e5                mov    %rsp,%rbp
   8:   50                      push   %rax
   9:   48 83 ec 08             sub    $0x8,%rsp
   d:   48 8d 45 08             lea    0x8(%rbp),%rax
  11:   48 89 45 f0             mov    %rax,-0x10(%rbp)
  15:   f4                      hlt    
  16:   90                      nop
  17:   48 83 c4 08             add    $0x8,%rsp
  1b:   58                      pop    %rax
  1c:   5d                      pop    %rbp
  1d:   48 cf                   iretq  
  1f:   90                      nop

0000000000000020 <_ZN3IDT5isr14EP14InterruptFramem>:
  20:   f3 0f 1e fa             endbr64 
  24:   55                      push   %rbp
  25:   48 89 e5                mov    %rsp,%rbp
  28:   50                      push   %rax
  29:   48 83 ec 10             sub    $0x10,%rsp
  2d:   48 8d 45 10             lea    0x10(%rbp),%rax
  31:   48 89 45 f0             mov    %rax,-0x10(%rbp)
  35:   48 8b 45 08             mov    0x8(%rbp),%rax
  39:   48 89 45 e8             mov    %rax,-0x18(%rbp)
  3d:   f4                      hlt    
  3e:   90                      nop
  3f:   48 83 c4 10             add    $0x10,%rsp
  43:   58                      pop    %rax
  44:   5d                      pop    %rbp
  45:   48 83 c4 08             add    $0x8,%rsp
  49:   48 cf                   iretq

I still wonder why doesn't the interrupt code push all registers?

Related