Saturday, January 22, 2011

MS Assembler does the right thing!

While working on OS373, something of note that I discovered during the interrupt work is that the MS Assembler is just 'following orders'.  Take for instance the following naked function written for CL:


void __declspec(naked) ISR()
{
       __asm pushad

       // Handle interrupt

       __asm popad
       __asm iretd
}


This is the basically the simplest Interrupt Service Routine (ISR) that can be constructed.  Now we know that the CL and LINK output 32 bit images by default.   What this means is that all opcodes generated by the compiler will be 32-bit.  However that may not always be the case.  Take the GCC inline assembler for instance.


void ISR()
{
//
       // Ignore Prolog
       //

       __asm__ ("pushad");

       // Handle interrupt

       __asm__ ("popad");
       __asm__ ("iret");

//
       // Ignore Epilog
//
}



Both code examples are the same.  In fact both will output the same opcodes, minus the prologue and epilogue in the GCC case (GCC does not support naked functions for the x86 architecture).  If you are looking closely you will notice that the GCC has an "iret" instruction instead of "iretd".  So how can they generated the same opcodes?  Well many assemblers consider them to be the same thing, even though "iret" is the 16-bit instruction and "iretd" the 32-bit, because the compilers infers which one to use depending on the bit-ness of the resulting binary.

If you look at the Intel Architecture guide vol 2a you read that even the underlying opcodes are the same, 0xCF.  However if in the CL example I replace the "iretd" with "iret" something very fishy happens.  MS takes the perspective that if you are writing in assembly you know what you want and it respects that, GCC makes some assumptions.  The resulting opcode for CL using the "iret" instruction will actually be, 0x66 0xCF, this is very different from 0xCF.  The 0x66 prefix overrides the default operand size (Intel Architecture guide vol 2a: chapter 2.1.1); in this case the input to the opcode is not a 32-bit argument but 16-bit.  The same can be done for 16-bit opcodes.  If you write in 16-bit assembler, you can prefix an opcode with 0x66 and the CPU will supply a 32-bit argument to the 16-bit opcode.  I had originally written the "iret" instruction because I ‘assumed’ that the MS Assembler was doing what the software guide suggested.  This cause all my interrupts to blow up.  The only way to track down this issue was to actually step through the kernel and examine the assembly generated for the ISR.

No comments:

Post a Comment