OSDev.org

The Place to Start for Operating System Developers
It is currently Tue Apr 23, 2024 1:02 am

All times are UTC - 6 hours




Post new topic Reply to topic  [ 10 posts ] 
Author Message
 Post subject: Interrupt called from 16 or 32 bit code
PostPosted: Sat Dec 09, 2006 11:15 am 
Offline
Member
Member
User avatar

Joined: Mon Dec 04, 2006 6:06 am
Posts: 158
Location: Berlin, Germany
I'm running in protected mode.

In a interrupt service routine (ISR) I know which mode the CPU is in, because the selector in the IDT descriptor is set in advance. But can I determine whether the interrupt was called from 16 or 32 bit code, so I can return properly?

The ISR is in 16 bit mode. An IRET will do the job if called from 16 bit code, but when called from 32 bit code I have to prefix the IRET with O32 (32 bit operand prefix).

Thanks.


Top
 Profile  
 
 Post subject: Re: Interrupt called from 16 or 32 bit code
PostPosted: Sat Dec 09, 2006 2:05 pm 
Offline
Member
Member
User avatar

Joined: Sat Jan 15, 2005 12:00 am
Posts: 8561
Location: At his keyboard!
Hi,

From Intel's System Programming Guide (the section on mixing 16-bit and 32-bit code):

Quote:
17.4.3 Interrupt Control Transfers

A program-control transfer caused by an exception or interrupt is always carried out through an interrupt or trap gate (located in the IDT). Here, the type of the gate (16-bit or 32-bit) determines the operand-size attribute used in the implicit call to the exception or interrupt handler procedure in another code segment.

A 32-bit interrupt or trap gate provides a safe inteface to a 32-bit exception or interrupt handler when the exception or interrupt occurs in either a 32-bit or 16-bit code segment. It is sometimes impractical, however, to place exception or interrupt handlers in 16-bit code segments, because only 16-bit return addresses are saved on the stack. If an exception or interrupt occurs in a 32-bit code segment when the EIP was greater than FFFFH, the 16-bit handler procedure cannot provide the correct return address.


Basically, the IRET you use should be the same regardless of what was interrupted (no operand size override is needed), and you'd better hope that EIP is never greater than 0xFFFF when a 32-bit code segment is interrupted.

Of course if EIP is never more than 0xFFFF then I'd have to wonder why you're using 32-bit code segments to begin with (just make all code segments 16-bit). If EIP can be greater than 0xFFFF then you need to use 32-bit interrupt handlers instead.

There is one other alternative - use "interrupt tasks" and do a full hardware task switch when an interrupt occurs and when you do IRET. That way the interrupted code's ESP and EIP would be saved in it's TSS and not on your 16-bit interrupt handler's 16-bit stack. This might not be good for performance though....


Cheers,

Brendan

_________________
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.


Top
 Profile  
 
 Post subject:
PostPosted: Sat Dec 09, 2006 7:04 pm 
Offline
Member
Member
User avatar

Joined: Mon Dec 04, 2006 6:06 am
Posts: 158
Location: Berlin, Germany
I don't get it. Then Bochs emulator must be wrong. I debugged the code. My interrupt routine is 16 bit and the selector in the IDT descriptor is a 16 bit selector. When doing an INT X from 32 bit code Bochs stores 32 bit words on the stack: EFlags, CS and EIP. When the interrupt routine is done and have to return, then IRET doesn't work because it only reads 16 bit words from the stack. It took me a long time to figure it out, but it seems it is the way Bochs does it.

So I'm a little confused now.

When calling the interrupt from 32 bit code, the EIP can be greater than 0xFFFF, but not when calling from 16 bit code. This is how I setup everything. From the quote you posted I shouldn't be able to do it at all because a 16 bit interrupt routine only will push 16 bit offset on the stack when called.

What I'm basicly trying is to make "simulated real mode" and 32 bit protected mode live in the same space. I know it is a bit silly. It is mostly for fun though. :)

I can call some BIOS interrupts, but I try to go a step further an also enable hardware interrupts. However it seems that INT X then can be called from both 16 and 32 bit mode, which gives me some problems.

Maybe interrupt tasks could help. I would have to read about them.


Top
 Profile  
 
 Post subject:
PostPosted: Sat Dec 09, 2006 9:00 pm 
Offline
Member
Member
User avatar

Joined: Sat Jan 15, 2005 12:00 am
Posts: 8561
Location: At his keyboard!
Hi,

Walling wrote:
I don't get it. Then Bochs emulator must be wrong. I debugged the code. My interrupt routine is 16 bit and the selector in the IDT descriptor is a 16 bit selector. When doing an INT X from 32 bit code Bochs stores 32 bit words on the stack: EFlags, CS and EIP. When the interrupt routine is done and have to return, then IRET doesn't work because it only reads 16 bit words from the stack. It took me a long time to figure it out, but it seems it is the way Bochs does it.

So I'm a little confused now.


I would assume Bochs doesn't handle this correctly - it's an unusual arrangement that no-one has probably tried before...

Walling wrote:
I can call some BIOS interrupts, but I try to go a step further an also enable hardware interrupts. However it seems that INT X then can be called from both 16 and 32 bit mode, which gives me some problems.


You could install 32-bit IRQ handlers that push return values on the stack and jump to the entry point for the BIOS's original real mode IRQ handlers. As long as your 32-bit IRQ handlers use EIP and ESP less than 0x10000 then the BIOS's IRQ handlers will return to your IRQ handlers, and your IRQ handlers can return correctly to anything that was interrupted.


Cheers,

Brendan

_________________
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.


Top
 Profile  
 
 Post subject:
PostPosted: Sat Dec 09, 2006 10:51 pm 
Offline
Member
Member

Joined: Sat Nov 25, 2006 6:33 am
Posts: 67
Location: PRC
Hi.

Bredan wrote:
You could install 32-bit IRQ handlers that push return values on the stack and jump to the entry point for the BIOS's original real mode IRQ handlers. As long as your 32-bit IRQ handlers use EIP and ESP less than 0x10000 then the BIOS's IRQ handlers will return to your IRQ handlers, and your IRQ handlers can return correctly to anything that was interrupted.


So far I haven't come across the problem in walling's case.

(Maybe what I write next is incorrect)But there are 3 versions of instructions for interrupt returning-i.e.,IRET,IRETW and IRETD(do they remind you of MOVSB,MOVSW and MOVSD?)If you want to return from a 16-bit interrupt handler to a 32-bit procedure,(I think) IRETD will work as expected.IRETD will pop a 32-bit address(a double word) to EIP,while the IRET will pop that according to the segment attribute on whether it's a 16-bit one or a 32-bit one(indicated by the instruction prefix) and IRETW will always pop a 16-bit address(a word) to the (E)IP.

To use IRETD,there're totally 12 bytes to be popped from the stack(in the following order):2 null bytes(for alignment) to be discarded,followed by 2 bytes to CS,followed by 4 bytes to EFLAG and the final 4 bytes to EIP.In this way,Bredan's method seems to be unnecessary(I think,because I haven't tried that yet :?).

If you think it's possible for your code to work with IRETD,you may have a try.

For more,you can refer to Intel® 64 and IA-32 Architectures Software Developer’s Manual:Volume 2A Instruction Set or NASM's documentation.


Hope for response :)


Last edited by m on Sun Dec 10, 2006 3:06 am, edited 1 time in total.

Top
 Profile  
 
 Post subject:
PostPosted: Sun Dec 10, 2006 1:54 am 
Offline
Member
Member
User avatar

Joined: Tue Oct 17, 2006 11:33 pm
Posts: 3882
Location: Eindhoven
IRET is an alias for whatever IRET? applies here (iirc). There's also IRETQ in 64-bit mode.


Top
 Profile  
 
 Post subject:
PostPosted: Sun Dec 10, 2006 4:12 am 
Offline
Member
Member
User avatar

Joined: Sat Jan 15, 2005 12:00 am
Posts: 8561
Location: At his keyboard!
Hi,

m wrote:
So far I haven't come across the problem in walling's case.


Wallings case is 32-bit protected mode code being interrupted by a 16-bit protected mode IRQ handler, where the CPU switches to 16-bit and then saves a 16-bit return EIP on the stack when the IRQ occurs. In this case the highest 16-bits of EIP are lost, making return impossible (unless the highest 16-bits of the return address are zero anyway).

Also, as Walling is trying to run real mode BIOS IRQ handlers using 16-bit protected mode, the size of the IRET (and the stack mangling needed to make it work with a non-default size) aren't possible as the code is in ROM. This means he can't design the 32-bit code to keep a copy of the highest 16-bits of EIP in a temporary locatoin and restore those bits before the IRET.

Of course the CPU and BIOS designers never intended to allow running real mode code as 16-bit protected mode, so I'm guessing Walling will keep running into problems (e.g. with BIOS code and segment registers), and sooner or later realise that implementing work-arounds for these problems is harder than setting up virtual 8086 mode (which is designed specifically for this).

I also think that getting it to work reliably isn't Walling's reason for attempting it, and that it'd be both fun and educational to try to get it to work (regardless of whether it does or not).

BTW I'd be tempted to pre-fill the GDT with 16-bit read/write data segment descriptors, such that GDT entry 0x0008 has a base address of 0x00000080, GDT entry 0x0010 has a base address of 0x00000100, etc, all the way up to GDT entry 0xFFF8 with a base address of 0x000FFF80. Then I'd use an LDT full of 16-bit code segment descriptors.

That way if the BIOS does "mov ax,0x0040; mov ds,ax" it'd actually work correctly. If the BIOS does "mov ax,cs; mov ds, ax" it'd generate a general protection fault when the BIOS tries to use DS, and the general protection fault handler could detect that DS is a code segment in the LDT and change DS to the equivelent data segment in the GDT. It won't fix all segment related problems, but it'd improve your chances..


Cheers,

Brendan

_________________
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.


Top
 Profile  
 
 Post subject:
PostPosted: Sun Dec 10, 2006 6:35 am 
Offline
Member
Member
User avatar

Joined: Mon Dec 04, 2006 6:06 am
Posts: 158
Location: Berlin, Germany
Thanks for your answers. What I have done so far is this:

Fill the GDT with 16 bit data descriptors with a 64 KiB limit like this: On location 0x0400 point to 0x4000. So you simulate the real mode segments: segment * 0x10 + offset. This is just like Brendan said.

At specific locations in the GDT I assume is not used by the BIOS I insert a 32 code and data selector for my 32 bit code and 16 bit code selectors for BIOS interrupts in the real mode IVT.

Then I create "bridges" for the real mode interrupts. These are needed because else I couldn't return to 32 bit code with an EIP bigger than 0xFFFF. So the IDT descriptors point to these "bridges", which is 16 bit code. They contain:
Code:
BITS 16
PUSHF ; Because IRET expects this
CALL 16-bit-selector:offset ; Call the BIOS interrupt service routine
IRETD ; similar to: O32 IRET

I create them at runtime and place them below 1 MiB, because of the IP/EIP problem. Below 1 MiB they can be identified by an offset below 0xFFFF using the simulated real mode segments. I insert the offset of the interrupt from the real mode IVT and use the right 16 bit code selector I defined in the GDT.

Now, enabling hardware interrupts still doesn't work. I have debugged a lot and it seems that the final IRETD is causing the problem. The IRETD assumes that the "bridge" was called from 32 bit code, but it seems that the interrupt can be called from 16 bit code as well (also theoretically by the BIOS code itself though I haven't seen it yet). I think that when a hardware interrupt triggers and the CPU is in 16 bit code it calls the interrupt as if it was in 16 bit code and only pushes 2-byte words on the stack (6 bytes in total). But IRETD expects 4-byte words (12 bytes in total). So my idea is that the "bridge" should identify which mode is was called from and then either return using IRETW or IRETD. It also seems that my stack selector (which is just the 32 bit data selector all the time) has no effect on whether 2- or 4-byte words are pushed/popped.

But if Bochs handles this all wrong and doesn't push the correct word sizes on the stack, then...... I don't know what to do. File a bug report maybe :) Another way you could do it then is to make the bridges in 32 bit code, because both the IP and EIP could be pushed on the stack. It depends on whether it is the calling code or the interrupt service routine that defines the word sizes pushed on the stack, when calling it.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Dec 11, 2006 5:24 am 
Offline
Member
Member

Joined: Sat Nov 25, 2006 6:33 am
Posts: 67
Location: PRC
Hi.

I think the BRIDGE you've described is exactly the interface for the 16-bit and 32-bit mixure.
Here's something very similar to your version I've found in Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3A: System Programming Guide, Part 1:

Quote:
16.4.5 Writing Interface Procedures
Placing interface code between 32-bit and 16-bit procedures can be the solution to
the following interface problems:
• Allowing procedures in 16-bit code segments to call procedures with offsets
greater than FFFFH in 32-bit code segments.
• Matching operand-size attributes between companion CALL and RET instructions.
• Translating parameters (data), including managing parameter strings with a
variable count or an odd number of 16-bit words.
• The possible invalidation of the upper bits of the ESP register.
The interface procedure is simplified where these rules are followed.
1. The interface procedure must reside in a 32-bit code segment (the D flag for the
code-segment descriptor is set).
2. All procedures that may be called by 16-bit procedures must have offsets not
greater than FFFFH.
3. All return addresses saved by 16-bit procedures must have offsets not greater
than FFFFH.
The interface procedure becomes more complex if any of these rules are violated. For
example, if a 16-bit procedure calls a 32-bit procedure with an entry point beyond
FFFFH, the interface procedure will need to provide the offset to the entry point. The
mapping between 16- and 32-bit addresses is only performed automatically when a
call gate is used, because the gate descriptor for a call gate contains a 32-bit
address. When a call gate is not used, the interface code must provide the 32-bit
address.
The structure of the interface procedure depends on the types of calls it is going to
support, as follows:
• Calls from 16-bit procedures to 32-bit procedures — Calls to the interface
procedure from a 16-bit code segment are made with 16-bit CALL instructions
(by default, because the D flag for the calling code-segment descriptor is clear),
and 16-bit operand-size prefixes are used with RET instructions to return from
the interface procedure to the calling procedure. Calls from the interface
procedure to 32-bit procedures are performed with 32-bit CALL instructions (by
default, because the D flag for the interface procedure’s code segment is set),
and returns from the called procedures to the interface procedure are performed
with 32-bit RET instructions (also by default).
• Calls from 32-bit procedures to 16-bit procedures — Calls to the interface
procedure from a 32-bit code segment are made with 32-bit CALL instructions
(by default), and returns to the calling procedure from the interface procedure
are made with 32-bit RET instructions (also by default). Calls from the interface
procedure to 16-bit procedures require the CALL instructions to have the
operand-size prefixes, and returns from the called procedures to the interface
procedure are performed with 16-bit RET instructions (by default).


Anything new?


Top
 Profile  
 
 Post subject:
PostPosted: Mon Dec 11, 2006 8:41 am 
Offline
Member
Member
User avatar

Joined: Mon Dec 04, 2006 6:06 am
Posts: 158
Location: Berlin, Germany
Thanks, m! Yes, it sounds like the way I'm doing it. However I'm not following rule 1, since the bridges are 16 bit code. I will make some tests with 32 bit bridges and see if I can benefit from it.

Currently I have another problem. It seems that some of the BIOS code use segment 0, but Bochs doesn't like to use GDT descriptor 0x0 so it triple faults.

I have written a heuristic test, which I will implement in my bridges. It looks on the stack and guess which mode it was called from. :)

However, I'm not sure that Bochs does it the right way. So my code might not run on a real computer. :? If indeed the IDT selector determined the word size pushed on the stack (when calling the interrupt) everything would be a lot easier.


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

All times are UTC - 6 hours


Who is online

Users browsing this forum: SemrushBot [Bot] and 113 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