Page 1 of 1

Exception Handler Bug

PostPosted: Mon Oct 20, 2008 6:54 pm
by Andyhhp
Hi,

When trying to test my exception handling routines, I had a stack problem and realized that it is a bug current in all the exception handling examples in the tutorials.

Here is an explanation and a solution to it.


When an interrupt fires, the stack looks like this:

...
EFlags
CS
EIP <-- esp points here
[Error] <---+ or here depending on the exception
...

However, given a function in C++ such as:
Code: Select all
void Div0(unsigned int eip,unsigned int cs,unsigned int eflags)
{
...
}

This is compiled expecting the stack to look like:

...
eflags
cs
eip
retn addr
prev ebp <-- ebp points here
local vars ...

The parameters are referenced using
Code: Select all
dword ptr [ebp +- num]


Therefore, the code that the C++ compiler and linker generates has a 4 byte offset because there is no return address for an interrupt handler. This causes all the parameters to index the wrong elements on the stack.

A workaround for this exists by subtracting 4 bytes from ebp so all the parameter references become correct. This has the side effect that any local variables will be 4 bytes lower in memory than expected but this will only have any effect if you try using inline assembly to reference them. One final point to say is that, because this isn't a naked function, the stack must be restored before the iret instruction.

Therefore, here is a function that works properly:
Code: Select all
void Div0(unsigned int eip,unsigned int cs,unsigned int eflags)
{
__asm sub ebp,4

//deal with Div0

__asm{
pop ebp
iretd
}
}


~Andrew

PostPosted: Tue Oct 21, 2008 1:00 am
by Mike
That (technically) was not a bug. The original reason it was omitted is because in the earlier demos all exception handlers simply halted the system thus we didnt need to worry about the stack.

Hm... Perhaps I shouldnt do that though. Ill see if I can update them. Thanks! :D

PostPosted: Tue Oct 21, 2008 9:51 am
by Andyhhp
Fair enough.

I came across this when trying to print out the exact address of the exception for debugging purposes.

Since I made this post, I have found a small problem that doesn't have a straight forward solution.

Because the register states need to be maintained exactly, and this is not a naked function, the compiler pushes all the used registers (other than eax) after the frame pointer and before the first line of code. It clears up again after itself before it returns (again except for eax).

The problem is that it doesn't see iretd as a return from the function so doesn't clear up before that.

The way I see it, there are 3 solutions, 2 are neither very elegent and 1 seems to go against the idea of using C++ over assembly.

1) Use naked functions and explicitly pusha/popa at the beginning and end. The problem with this is you can't have local variables.

2) Use the generated asm code from the compiler to see which registers need to be pop'd before iretd and hope that adding that code doesn't cause the optimizer to use different registers. The problem with this is that it messes with local variables because the the stack is changing in the area where local variables are stored

3) Write a small function entirely in assembly that handles the stack properly, which calls the normal C/C++ exception handler.

I am currently working on implementing #3 and will post an example when I have it fully working (i currently have it using C++ naming, as well as namespaces but I am working on getting it to call other C++ named functions).

~Andrew

PostPosted: Tue Oct 21, 2008 11:47 am
by Mike
Because the register states need to be maintained exactly, and this is not a naked function, the compiler pushes all the used registers (other than eax) after the frame pointer and before the first line of code. It clears up again after itself before it returns (again except for eax).

The problem is that it doesn't see iretd as a return from the function so doesn't clear up before that.

You need to use an _asm iretd instruction before the routines end as the compiler will not do that for you.

If your routines are using the _cdecl calling convention, the parameter list for the routine contains the values pushed by the processor on the current stack. You will need to clean up the values pushed by the compiler manually. This is fairly easy. I can see if I can post an example later if you want (cant do it now--got to go to work :) )

PostPosted: Tue Oct 21, 2008 12:17 pm
by Andyhhp
lol - thats what I intended to say - i wasn't very clear.

The problem with manually clearing up the stack below EIP/Error is that because of optimization, adding the relevant pop instructions to maintain all the registers sometimes causes the compiler to change which registers it uses, which still leaves you with stack corruption.

I have found a way to create asm stub functions that will properly maintain the stack, then call proper C++ functions as handlers.

However, I have a hardware lab to go to now so I shall post details when I get back

~Andrew

PostPosted: Wed Oct 22, 2008 12:43 am
by Andyhhp
Here is my solution. I will admit it is fairly heavily compiler dependent but the initialization functions and some of the setup is as well so meh - it will have to do ^_^

Firstly, I will explain the structure I have taken in my code which may help to explain the way I went about this.

I am structuring my code in a way not unlike the java libraries. As such, all the code for interrupt and exception handling is in namespace called ISR.

Specifically, I have a function called Ex_Div0 that is nominally the int0 interrupt handler.

However, due to the stack problems that I illustrated in my first post, and the repeated problems I discovered after, I have had to implement this strategy to solve the problem. The good news that I believe I have fully tested this now so I don't foresee any more problems (is only but its nice to hope)

Here is the relevant snippets of my code:

isr.cpp
Code: Select all
namespace ISR {

struct IntStk
{
   unsigned int eip;
   unsigned int cs;
   unsigned int eflags;
};

struct EIntStk
{
   unsigned int error;
   unsigned int eip;
   unsigned int cs;
   unsigned int eflags;
};

void Ex_Div0Stub();
void Ex_Div0(void *ptr);

void Initialize(){
...
   Install_ISR(0,(ISR_FUNCTION)Ex_Div0Stub,0x08,flags);
...
}

void Ex_Div0(void *ptr){
      IntStk * data = (IntStk*)ptr;

      Print("\n\nDiv0 Exception occurred - Addr: 0x");
      Hex2Str(data->cs);
      Print(":0x");
      Hex2Str(data->eip);
      Print(", EF: 0x");
      Hex2Str(data->eflags);
      Print('\n');

      unsigned char ins = *((unsigned char*)data->eip);
      if(ins == 0xF7 || ins == 0xF6)
      {
         PrintLn("Skipping exceptional opcode");
         data->eip += 2;
      }
      else
         PrintLn("No exceptional opcode found - returning");
}

};

2 functions, Ex_Div0 and Ex_Div0Stub are declared but only Ex_Div0 is defined. It is a simple function that prints out the offending address, then checks to see wether the offending opcode is a div/idiv instruction (as opposed to an int0) and if so, adds 2 to the instruction pointer to skip it (not ideal but best until I have C++ language exception handling sorted). Then this function returns normally. Notice that the Ex_Div0Stub function is the one set as the interrupt handler, not Ex_Div0 itself.

isrstub.asm
Code: Select all
TITLE isrstub.asm
.686P
.model flat

?EX_Div0@ISR@@YAXPAX@Z PROTO SYSCALL

_TEXT SEGMENT
?Ex_Div0Stub@ISR@@YAXXZ PROC

   push eax
   
   lea eax,[esp+4]
   push eax
   call ?EX_Div0@ISR@@YAXPAX@Z
   add esp,4
   
   pop eax
    iretd
   
?Ex_Div0Stub@ISR@@YAXXZ ENDP
_TEXT ENDS

END

This is a .asm file which, when included into an MSCV project, automatically get assembled (instead of compiled) then linked into the final program. The top 3 lines are setup parameters for the file. I used the same settings that the compiler generates and spits out in the code listings, in an attempt to preemptively prevent any bugs due to code of mis-matched versions.

The line "?EX_Div0@ISR@@YAXPAX@Z PROTO SYSCALL" declares the name mangled "void Ex_Div0(void *ptr)" as a function. Similarly, "?Ex_Div0Stub@ISR@@YAXXZ PROC" defines the name mangled "void Ex_Div0Stub()". By using these names, the linker links them together and all is fine as far function pointers are concerned.

The ?Ex_Div0Stub@ISR@@YAXXZ function itself is the interrupt handler. It stored the value eax, the only register which the compiler wont maintain in the Ex_Div0 function. It then pushes a pointer to the start of the stack information from the interrupt as the void* parameter for Ex_Div0. Then it calls the function itself. After the function returns, it restores eax and calls iretd.

By passing the address of the interrupt stack information as a parameter into the Ex_Div0 function, it allows that function to alter things like the return address if needs be. By the parameter being a void pointer, it allows it to determine whether or not an error code was pushed on the stack (important for some handlers) rather than assuming one will be present or not depending on the way its coded.


I hope this is clear but feel free to ask questions about any confusion/misunderstandings.

~Andrew

PostPosted: Sat Nov 01, 2008 11:53 pm
by Mike
Im going to see if we can get the next demo's exception handlers properly working. If it works, Ill update all of the previous demos.

I dont plan on releasing the next tutorial's demo until its working, so lets hope for the best... :)

PostPosted: Sun Nov 02, 2008 12:01 am
by Andyhhp
Yeah - I'm sure the tutorial on paged memory management would go better with a functioning page fault handler

Sadly, due to work, I have yet to continue this method beyond the div0 handler but I should be continuing soon.

If there are any problems with this method, please say so I can modify my own ^_^

Thanks,

Andrew

PostPosted: Sat Nov 08, 2008 12:01 am
by michael
Wow. Thanks. :) I've been trying for ages to make a keyboard interrupt handler that worked but it always crashed on iret, and this fixes that problem too.. although its really the same sortof thing. (If you cant read this properly my browsers being very anoying right now :x )

PostPosted: Sat Nov 08, 2008 11:14 pm
by Andyhhp
Lol np - I posted it here to help people. Glad to see it's appreciated.

~Andrew

Re: Exception Handler Bug

PostPosted: Sat Aug 13, 2011 10:17 pm
by Mezo
Thank you very much Andyhhp :D
you helped me very much :D