OSDev.org

The Place to Start for Operating System Developers
It is currently Thu Mar 28, 2024 1:45 pm

All times are UTC - 6 hours




Post new topic Reply to topic  [ 9 posts ] 
Author Message
 Post subject: (Solved) Broken exception handler
PostPosted: Sun Sep 10, 2017 9:17 am 
Offline
Member
Member
User avatar

Joined: Fri Aug 07, 2015 6:13 am
Posts: 1134
Hello to people that speak Assembly, I need your help.

For the past couple of days I've been deeply researching the interrupt section of x86 processors.
As you are probably familiar with the fact that most of the people just slam some tutorial code together and somehow make it work. There isn't much that you can look at.
I am really struggling with my common interrupt exception handler (aka ISRs, I am not talking about interrupt requests doe), I am trying to understand how and why it works and I seem to be missing something crucial.
This is how it all looks inside my head:
1.When an interrupt exception gets triggered the CPU does this:
a) accesses the IDT and looks for the corresponding gate descriptor and if found calls it
b) pushes: SS (only valid when doing privilege level switching or in user mode), user_esp (only valid when doing privilege level switching or in user mode), EFLAGS, CS, EIP, Error Code (only some exceptions have it, if not I have to push a fake one).
2.I also need to push some things:
a) an interrupt number
b) general purpose registers (except EIP and ESP)
c) segment registers (except SS and CS)
3.I need to call an external high level handler
4.When it returns I need to:
a) pop everything back
b) add 8 bytes (2 variables worth) to ESP (effectively pop-ing the error code and interrupt number)
c) do an interrupt return call

I have a couple of distinguishable issues:
1.Exception 0 == Exception 6
Code:
When I try to cause a division by zero error:
uint32_t test = 10;
uint32_t test_d = 0;
uint32_t something = test / test_d;
some_print_function(somethig, ...);
I get everything but division by zero...
But when I do this it works fine:
__asm__(int $0);

2.Infinite exceptions, my handler gets called over and over and over
3.Only some values are correct, e.g. EAX, EDX, DS, FS, GS, CS are okay everything else is not, or it is of by a certain amount (EFLAGS in this case).

This is my code so far:
Code:
   push eax
   push ecx
   push edx
   push ebx
   push ebp
   push esi
   push edi
   push ds
   push es
   push fs
   push gs
   ;There is something that needs to go in here
   call C_Interrupt_Service_Routine_Handler
   ;There is something that needs to go in here
   pop gs
   pop fs
   pop es
   pop ds
   pop edi
   pop esi
   pop ebp
   pop ebx
   pop edx
   pop ecx
   pop eax
   add esp, 4
   add esp, 4
   iretd


I know for sure that something must go in there, because after calling that function, ESP and EIP are going to change (according to Intel and common sense).
I've tried many solution but I don't know why would they work since I am not an Assembly guy.
Code:
push esp
call C_Interrupt_Service_Routine_Handler
add esp, 4 (4 bytes because x86)

This does not fix my issues.

Both my GDT and IDT are set up correctly. (all my code, no tutorials only Intel manuals).
No, I did not forget a pointer. (registers_t* registers)

I need a solution for those problems and most importantly WHY does that specific solution work. (that is what I want to learn).
I hope that this post was informative enough for you guys to help me.
Btw this is what happens when I mess with Assembly, but I decided to mess with it and try to learn from my own mistakes.
Thanks!

_________________
OS: Basic OS
About: 32 Bit Monolithic Kernel Written in C++ and Assembly, Custom FAT 32 Bootloader


Last edited by Octacone on Sun Sep 17, 2017 3:17 am, edited 1 time in total.

Top
 Profile  
 
 Post subject: Re: Broken exception handler
PostPosted: Sun Sep 10, 2017 9:42 am 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5100
Octacone wrote:
1.Exception 0 == Exception 6
Code:
uint32_t test = 10;
uint32_t test_d = 0;
uint32_t something = test / test_d;

Your compiler is smart enough to see that "test / test_d" is division by zero. In C, division by zero is undefined, and your compiler reacts to undefined behavior by generating an illegal opcode.

Octacone wrote:
2.Infinite exceptions, my handler gets called over and over and over

If it's exception 6, then your interrupt handler is working but you keep returning to the same illegal opcode.

Octacone wrote:
3.Only some values are correct, e.g. EAX, EDX, DS, FS, GS, CS are okay everything else is not, or it is of by a certain amount (EFLAGS in this case).

How do you know the values are wrong? That's not enough detail to guess at what the problem might be.

Now might be a good time to check what kind of stack alignment your compiler expects (most likely something stricter than the System V ABI's 4-byte alignment) and either adjust your assembly code or add compiler directives to compensate. I don't think this will fix any of your problems, but it may prevent bugs further down the line.


Top
 Profile  
 
 Post subject: Re: Broken exception handler
PostPosted: Sun Sep 10, 2017 9:55 am 
Offline
Member
Member
User avatar

Joined: Fri Aug 07, 2015 6:13 am
Posts: 1134
Octocontrabass wrote:
Your compiler is smart enough to see that "test / test_d" is division by zero. In C, division by zero is undefined, and your compiler reacts to undefined behavior by generating an illegal opcode.


Now it makes sense. Then it is not an issue since invalid opcode = exception 6.

Octocontrabass wrote:
If it's exception 6, then your interrupt handler is working but you keep returning to the same illegal opcode.


Hmm. I definitely am returning somewhere. But why? Why would it happen again over and over.

Octocontrabass wrote:
How do you know the values are wrong? That's not enough detail to guess at what the problem might be.

Now might be a good time to check what kind of stack alignment your compiler expects (most likely something stricter than the System V ABI's 4-byte alignment) and either adjust your assembly code or add compiler directives to compensate. I don't think this will fix any of your problems, but it may prevent bugs further down the line.


I used Bochs for testing them. Some values were matching and some were not.
My stack is aligned like this:
Code:
align 16

I don't understand the difference anyways. There is not a single article about it. Should I use 4 instead and why would I?

_________________
OS: Basic OS
About: 32 Bit Monolithic Kernel Written in C++ and Assembly, Custom FAT 32 Bootloader


Top
 Profile  
 
 Post subject: Re: Broken exception handler
PostPosted: Sun Sep 10, 2017 10:23 am 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5100
Octacone wrote:
Hmm. I definitely am returning somewhere. But why? Why would it happen again over and over.

You're returning to the opcode that caused the exception, so it causes the same exception again. The CPU can't know how long an invalid opcode is to skip it because the opcode is invalid.

Octacone wrote:
I used Bochs for testing them. Some values were matching and some were not.

Where in your code did you pause execution to compare?

Octacone wrote:
My stack is aligned like this:

That only affects how the space for your stack is allocated. I'm referring to the value of ESP, which your compiler most likely assumes is aligned a specific way when the function is called. Since the CPU always adjusts ESP in multiples of 4, keeping 4-byte alignment is easy, but keeping 16-byte alignment is not.

Octacone wrote:
I don't understand the difference anyways. There is not a single article about it. Should I use 4 instead and why would I?

Stack alignment doesn't make much of a difference until you start getting into SIMD, where the compiler might generate opcodes that require aligned operands for accessing variables stored on the stack. You can make the compiler deal with stack alignment issues automatically with the force_align_arg_pointer function attribute. (Note that it generates bigger/slower code, so you should only use this attribute on functions that are called from assembly.) It's mentioned on the System V ABI article.


Top
 Profile  
 
 Post subject: Re: Broken exception handler
PostPosted: Sun Sep 10, 2017 12:00 pm 
Offline
Member
Member
User avatar

Joined: Fri Aug 07, 2015 6:13 am
Posts: 1134
Octocontrabass wrote:
Octacone wrote:
Hmm. I definitely am returning somewhere. But why? Why would it happen again over and over.

You're returning to the opcode that caused the exception, so it causes the same exception again. The CPU can't know how long an invalid opcode is to skip it because the opcode is invalid.

Octacone wrote:
I used Bochs for testing them. Some values were matching and some were not.

Where in your code did you pause execution to compare?

Octacone wrote:
My stack is aligned like this:

That only affects how the space for your stack is allocated. I'm referring to the value of ESP, which your compiler most likely assumes is aligned a specific way when the function is called. Since the CPU always adjusts ESP in multiples of 4, keeping 4-byte alignment is easy, but keeping 16-byte alignment is not.

Octacone wrote:
I don't understand the difference anyways. There is not a single article about it. Should I use 4 instead and why would I?

Stack alignment doesn't make much of a difference until you start getting into SIMD, where the compiler might generate opcodes that require aligned operands for accessing variables stored on the stack. You can make the compiler deal with stack alignment issues automatically with the force_align_arg_pointer function attribute. (Note that it generates bigger/slower code, so you should only use this attribute on functions that are called from assembly.) It's mentioned on the System V ABI article.


1. Does that mean that it is impossible to stop it? Aka it will always loop? Halting doesn't help.
2. Bochs stops it automatically once it sees an exception. EDIT: or maybe not?
3. I don't use anything special such as SSE, MMX, SIMD so I don't really need to care about it right now.
4. That article says that whenever I issue a CALL instruction the CPU will push the next EIP value onto stack so it can be returned to and executed later. But what am I supposed to do with that information.
How can it help me to run the next instruction? How much is the stack screwed up after that CALL? So if I preserve ESP and then pop it back it will automatically executed it again and again.

Here is something strange:
When doing __asm__("int $n"); it works only for those exception that don't have an error code aka those whose error code I've pushed manually. Everything else infinitely loops.

_________________
OS: Basic OS
About: 32 Bit Monolithic Kernel Written in C++ and Assembly, Custom FAT 32 Bootloader


Top
 Profile  
 
 Post subject: Re: Broken exception handler
PostPosted: Sun Sep 10, 2017 3:11 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5100
Octacone wrote:
1. Does that mean that it is impossible to stop it? Aka it will always loop? Halting doesn't help.

Your exception handler's job is to figure out what's wrong and either fix it or permanently stop.

For example, it's very common to use page faults to do demand paging. In the page fault handler, you figure out which page needs to be mapped and map it. Then, when you return, it doesn't cause another page fault so it doesn't get stuck in a loop.

On the other hand, a divide error is usually unrecoverable. For a userspace application that causes a divide error, you'll most likely want to terminate the offending program. If your kernel causes a divide error, you'll probably halt the kernel and display an error message on the screen.

Octacone wrote:
2. Bochs stops it automatically once it sees an exception. EDIT: or maybe not?

Bochs doesn't stop on exceptions. For now, you might want to put an infinite loop in your exception handler.

Octacone wrote:
3. I don't use anything special such as SSE, MMX, SIMD so I don't really need to care about it right now.

Maybe. It can't hurt to take care of it now so you don't end up dealing with it later.

Octacone wrote:
4. That article says that whenever I issue a CALL instruction the CPU will push the next EIP value onto stack so it can be returned to and executed later. But what am I supposed to do with that information.
How can it help me to run the next instruction? How much is the stack screwed up after that CALL? So if I preserve ESP and then pop it back it will automatically executed it again and again.

I'm not sure what you're talking about here. Is it the description of the CALL instruction on the System V ABI page? If so, then you shouldn't worry about it: the function you call will return with RET, so execution resumes immediately following the CALL instruction with the stack left the same way it was before the CALL. What you're supposed to take away from that description is the reminder that CALL pushes a value onto the stack, which affects the stack alignment. If you tell the compiler to align it for you, then you don't need to worry about aligning the stack either.

Octacone wrote:
Here is something strange:
When doing __asm__("int $n"); it works only for those exception that don't have an error code aka those whose error code I've pushed manually. Everything else infinitely loops.

The INT instruction acts basically the same as a hardware interrupt. Since it's not an exception, it doesn't push an error code, even if you use the same interrupt number as an exception.


Top
 Profile  
 
 Post subject: Re: Broken exception handler
PostPosted: Mon Sep 11, 2017 12:41 pm 
Offline
Member
Member
User avatar

Joined: Fri Aug 07, 2015 6:13 am
Posts: 1134
Octocontrabass wrote:
Octacone wrote:
1. Does that mean that it is impossible to stop it? Aka it will always loop? Halting doesn't help.

Your exception handler's job is to figure out what's wrong and either fix it or permanently stop.

For example, it's very common to use page faults to do demand paging. In the page fault handler, you figure out which page needs to be mapped and map it. Then, when you return, it doesn't cause another page fault so it doesn't get stuck in a loop.

On the other hand, a divide error is usually unrecoverable. For a userspace application that causes a divide error, you'll most likely want to terminate the offending program. If your kernel causes a divide error, you'll probably halt the kernel and display an error message on the screen.


Good. For now I will just shut the entire thing down since I don't have any userspace code, yet. I don't know about demand paging doe, I don't want users to map memory that is not permitted to be mapped. I can just simply map it right away and if I get a page fault, there is something wrong with my code.
I guess I will mostly use the GPF exception since it is needed for a virtual 8086 monitor. All other exception can be terminated or their processes can be completely removed form the process list.
You can't do much about it anyways. You can fix your code, but you can't predict somebody else's mistake.

Octocontrabass wrote:
Octacone wrote:
2. Bochs stops it automatically once it sees an exception. EDIT: or maybe not?

Bochs doesn't stop on exceptions. For now, you might want to put an infinite loop in your exception handler.


I figured it out eventually, it was something else that was stopping it automatically.

Octocontrabass wrote:
Octacone wrote:
3. I don't use anything special such as SSE, MMX, SIMD so I don't really need to care about it right now.

Maybe. It can't hurt to take care of it now so you don't end up dealing with it later.


How can I do it. You said it would increase the complexity and size of my code. If I tell GCC to use fno-emit-pointer-align-something it will adapt automatically but I don't want it for all the code. Maybe only for MMX and SSE.

Octocontrabass wrote:
Octacone wrote:
4. That article says that whenever I issue a CALL instruction the CPU will push the next EIP value onto stack so it can be returned to and executed later. But what am I supposed to do with that information.
How can it help me to run the next instruction? How much is the stack screwed up after that CALL? So if I preserve ESP and then pop it back it will automatically executed it again and again.

I'm not sure what you're talking about here. Is it the description of the CALL instruction on the System V ABI page? If so, then you shouldn't worry about it: the function you call will return with RET, so execution resumes immediately following the CALL instruction with the stack left the same way it was before the CALL. What you're supposed to take away from that description is the reminder that CALL pushes a value onto the stack, which affects the stack alignment. If you tell the compiler to align it for you, then you don't need to worry about aligning the stack either.


Yes I was talking about. It kind of does screw it, but if you pop everything back after that function it should be fine sort of.

Octocontrabass wrote:
Octacone wrote:
Here is something strange:
When doing __asm__("int $n"); it works only for those exception that don't have an error code aka those whose error code I've pushed manually. Everything else infinitely loops.

The INT instruction acts basically the same as a hardware interrupt. Since it's not an exception, it doesn't push an error code, even if you use the same interrupt number as an exception.


It makes sense. If it doesn't push an error code the handler won't function properly since it is only configured to take care of those two extra dwords (error code, interrupt number), but not one which messes it up.

I actually managed to fix it. I just had to halt the CPU inside the C handler so it doesn't return and everything worked fine afterwards.
The only concern I have is how do I check if my values are okay or not. Is there a definite way to do it? If I pause within Bochs the values displayed might actually be those that are currently being used by the C handler.
Thanks for assisting me anyways. I managed to kind of sort out some things in my head.

_________________
OS: Basic OS
About: 32 Bit Monolithic Kernel Written in C++ and Assembly, Custom FAT 32 Bootloader


Top
 Profile  
 
 Post subject: Re: Broken exception handler
PostPosted: Mon Sep 11, 2017 2:47 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5100
Octacone wrote:
I don't know about demand paging doe, I don't want users to map memory that is not permitted to be mapped.

So... don't let them? It's your kernel, you can kill the user program if it tries to do something that isn't allowed.

Octacone wrote:
How can I do it. You said it would increase the complexity and size of my code. If I tell GCC to use fno-emit-pointer-align-something it will adapt automatically but I don't want it for all the code. Maybe only for MMX and SSE.

To tell GCC to align the stack, use the force_align_arg_pointer function attribute like this:
Code:
__attribute__((force_align_arg_pointer)) void kmain(...) {...}
__attribute__((force_align_arg_pointer)) void c_isr_handler(registers_t* registers) {...}

You should place it only on functions that you call from assembly, to minimize the extra code generated for stack alignment.

If you'd rather align the stack in assembly, you might have to get clever. For example, something like this to call your C ISR handler:
Code:
mov ebx, esp       ; save the stack pointer before alignment
sub esp, 4         ; reserve space for one argument
and esp, ~(16-1)   ; align the stack
mov [esp], ebx     ; put the registers_t* argument on the stack
call c_isr_handler ; C functions will preserve ebx, esi, edi, ebp, and esp
mov esp, ebx       ; clean up the argument and restore the saved stack pointer

For situations where the stack pointer has a known value beforehand, like when your startup code calls your main function, you can adjust the stack pointer by a fixed value to ensure alignment.

Octacone wrote:
Yes I was talking about. It kind of does screw it, but if you pop everything back after that function it should be fine sort of.

When the function you call returns, ESP has the same value it did before the CALL instruction. Where's the problem? I really don't understand what you think is happening here.

Octacone wrote:
The only concern I have is how do I check if my values are okay or not. Is there a definite way to do it? If I pause within Bochs the values displayed might actually be those that are currently being used by the C handler.

Use a breakpoint to pause at the point where the values are pushed onto the stack. If the C handler prints the same values that your assembly pushes onto the stack, then it's working correctly.


Top
 Profile  
 
 Post subject: (Solved) Broken exception handler
PostPosted: Sun Sep 17, 2017 3:17 am 
Offline
Member
Member
User avatar

Joined: Fri Aug 07, 2015 6:13 am
Posts: 1134
Octocontrabass wrote:
Octacone wrote:
I don't know about demand paging doe, I don't want users to map memory that is not permitted to be mapped.

So... don't let them? It's your kernel, you can kill the user program if it tries to do something that isn't allowed.

Octacone wrote:
How can I do it. You said it would increase the complexity and size of my code. If I tell GCC to use fno-emit-pointer-align-something it will adapt automatically but I don't want it for all the code. Maybe only for MMX and SSE.

To tell GCC to align the stack, use the force_align_arg_pointer function attribute like this:
Code:
__attribute__((force_align_arg_pointer)) void kmain(...) {...}
__attribute__((force_align_arg_pointer)) void c_isr_handler(registers_t* registers) {...}

You should place it only on functions that you call from assembly, to minimize the extra code generated for stack alignment.

If you'd rather align the stack in assembly, you might have to get clever. For example, something like this to call your C ISR handler:
Code:
mov ebx, esp       ; save the stack pointer before alignment
sub esp, 4         ; reserve space for one argument
and esp, ~(16-1)   ; align the stack
mov [esp], ebx     ; put the registers_t* argument on the stack
call c_isr_handler ; C functions will preserve ebx, esi, edi, ebp, and esp
mov esp, ebx       ; clean up the argument and restore the saved stack pointer

For situations where the stack pointer has a known value beforehand, like when your startup code calls your main function, you can adjust the stack pointer by a fixed value to ensure alignment.

Octacone wrote:
Yes I was talking about. It kind of does screw it, but if you pop everything back after that function it should be fine sort of.

When the function you call returns, ESP has the same value it did before the CALL instruction. Where's the problem? I really don't understand what you think is happening here.

Octacone wrote:
The only concern I have is how do I check if my values are okay or not. Is there a definite way to do it? If I pause within Bochs the values displayed might actually be those that are currently being used by the C handler.

Use a breakpoint to pause at the point where the values are pushed onto the stack. If the C handler prints the same values that your assembly pushes onto the stack, then it's working correctly.


Thanks for helping me out. I think I found a way to fix everything.
Paging is currently not my concern since it has to be redone to support PAE anyways.
GCC magic seems reasonable to me. I don't need it yet, once I do get to need it I know what to look for.
Yes it does have the same stack pointer. The only thing that has to change is EIP, I am sure. I noticed that myself.
I have put a magic breakpoint just before calling the main C handler, every registers matches except for EFLAGS: originally (0x200012), displayed as (0x210012). That could be normal, I don't know. It doesn't concern me too much.

Edit: I managed to figure out how C and Assembly pass arguments around. CR0, CR2, CR3, CR4 are now being preserved too.

_________________
OS: Basic OS
About: 32 Bit Monolithic Kernel Written in C++ and Assembly, Custom FAT 32 Bootloader


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 9 posts ] 

All times are UTC - 6 hours


Who is online

Users browsing this forum: Bing [Bot], Google [Bot] and 70 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group