It's not entirely clear what you want to accomplish. it seems like you want an interrupt handler that does the iret
without other pushes and pops by default.
GCC
Using GCC (without NASM) something like this is possible:
/* Make C extern declarations of the ISR entry points */
extern void isr_test1(void);
extern void isr_test2(void);
/* Define a do nothing ISR stub */
__asm__(".global isr_test1
"
"isr_test1:
"
/* Other stuff here */
"iret");
/* Define an ISR stub that makes a call to a C function */
__asm__(".global isr_test2
"
"isr_test2:
"
"cld
" /* Set direction flag forward for C functions */
"pusha
" /* Save all the registers */
/* Other stuff here */
"call isr_test2_handler
"
"popa
" /* Restore all the registers */
"iret");
void isr_test2_handler(void)
{
return;
}
Basic __asm__
statements in GCC can be placed outside of a function. We define labels for our Interrupt Service Routines (ISRs) and make them externally visible with .globl
(You may not need global visibility but I show it anyway).
I create a couple of sample interrupt service routines. One that does nothing more than an iret
and the other that makes a function call to a C handler. We save all the registers and restore them after. C functions require the direction flag be set forward so we need a CLD before calling the C function. This sample code works for 32-bit targets. 64-bit can be done by saving the registers individually rather than using PUSHA and POPA.
Note: If using GCC on Windows the function names inside the assembly blocks will likely need to be prepended with an _
(underscore). It would look like:
/* Make C extern declarations of the ISR entry points */
extern void isr_test1(void);
extern void isr_test2(void);
/* Define a do nothing ISR stub */
__asm__(".global _isr_test1
"
"_isr_test1:
"
/* Other stuff here */
"iret");
/* Define an ISR stub that makes a call to a C function */
__asm__(".global _isr_test2
"
"_isr_test2:
"
"cld
" /* Set direction flag forward for C functions */
"pusha
" /* Save all the registers */
/* Other stuff here */
"call _isr_test2_handler
"
"popa
" /* Restore all the registers */
"iret");
void isr_test2_handler(void)
{
return;
}
MSVC/MSVC++
Microsoft's C/C++ compilers support the naked attribute on functions. They describe this attribute as:
The naked storage-class attribute is a Microsoft-specific extension to the C language. For functions declared with the naked storage-class attribute, the compiler generates code without prolog and epilog code. You can use this feature to write your own prolog/epilog code sequences using inline assembler code. Naked functions are particularly useful in writing virtual device drivers.
An example Interrupt Service Routine could be done like this:
__declspec(naked) int isr_test(void)
{
/* Function body */
__asm { iret };
}
You'll need to deal with the issues of saving and restoring registers, setting the direction flag yourself in a similar manner to the GCC example above.
GCC 7.x+ introduced Interrupt Attribute on x86/x86-64 Targets
On GCC 7.0+ you can now use __attribute__((interrupt))
on functions. This attribute was only recently supported on x86 and x86-64 targets:
interrupt
Use this attribute to indicate that the specified function is an interrupt handler or an exception handler (depending on parameters passed to the function, explained further). The compiler generates function entry and exit sequences suitable for use in an interrupt handler when this attribute is present. The IRET instruction, instead of the RET instruction, is used to return from interrupt handlers. All registers, except for the EFLAGS register which is restored by the IRET instruction, are preserved by the compiler. Since GCC doesn’t preserve MPX, SSE, MMX nor x87 states, the GCC option -mgeneral-regs-only should be used to compile interrupt and exception handlers.
This method still has deficiencies. If you ever want your C code to access the contents of a register as they appeared at the time of the interrupt, there is currently no reliable way to do it with this mechanism. This would be handy if you were writing a software interrupt and needed access to the registers to determine what actions to take (ie: int 0x80
on Linux). Another example would be to allow an interrupt to dump all the register contents to the display for debug purposes.