11

The Intel 80386 CPU didn't have an on-board x87 FPU (maybe with the exception of some non-Intel clones). It was, however, able to use either a 80287 or 80387 as an external FPU. When the x87 FPU accesses memory, the CPU makes the necessary privilege checks and generates an exception if the access is illegal (not enough privilege, page/segment not present etc.).

On CPUs with an on-board FPU, the exception generated is the General Protection Fault (interrupt 13), just like all other non-FPU illegal accesses.

On CPUs without an on-board FPU (like the 80386), the exception generated is the Coprocessor Segment Overrun Exception (interrupt 9), unlike all other memory accesses which use the GPF.

The Intel i386 manual states the following about how INT9 should be handled (emphasis is mine):

The addresses of the failed numeric instruction and its operand may be lost; a FSTENV instruction does not return reliable numeric coprocessor state information. The coprocessor-segment-overrun exception should be handled by executing a FNINIT instruction (i.e., a FINIT instruction without a preceding WAIT instruction). The return address on the stack might not point to either the failed numeric instruction or the instruction following the failed numeric instruction. The failed numeric instruction is not restartable; however, the interrupted task may be restartable if it did not contain the failed numeric instruction.

What I understand from this is that if the FPU makes an illegal memory access then its entire state cannot be recovered. Am I actually getting this right?

What happens if the illegal access happened, for example, because the memory was swapped to disk? Normally the OS would load the missing page to memory, put it back in the virtual address space and continue execution. However, with this behavior, it is impossible to do that because ‘The failed numeric instruction is not restartable’. You cannot get the FPU state either, all that can be done is to FNINIT and kill the innocent application.

I also don't understand this statement very well: ‘The return address on the stack might not point to either the failed numeric instruction or the instruction following the failed numeric instruction’. Does this mean that the return address is undefined and all the OS can do is kill the application and return to some other task by crafting a return address on the stack?

11
  • 1
    Is a segment overrun exception generated on a page fault, or merely in the particular case of a segment overrun? The 80286 segment model was based on the assumption that entire segments would be swapped in and out as a unit, so there was no need to handle the possibility that an access intended for the FPU might straddle the end of a segment.
    – supercat
    Oct 22, 2021 at 20:49
  • 1
    Based on my reading of that section, the processor checks that the first and last bytes of the operand are within addressable memory before telling the FPU to expect data. If neither is within addressable storage, a normal fault would occur. The error that triggers an unrecoverable segment overrun fault can only happen under weird (almost certainly contrived) scenarios where the first and last bytes of the operand would have valid addresses but some bytes in the middle would not. Ordinary page faults that could hit a middle byte would also hit on a first or last byte, allowing normal handling.
    – supercat
    Oct 22, 2021 at 21:06
  • 1
    So basically, an obscure corner case that they weren't able to design a good solution for, so they decided to essentially just have the CPU give up, and they documented that. The message between the lines in the documentation is "This can only happen if your OS manages memory in a dumb way, so don't do that". Oct 23, 2021 at 18:35
  • 5
    I have the 1986 edition which gives more explicit advice: align segment starts on page boundaries, and don't make segments that are just slightly less than maximum size. It then says "If neither software system design constraint is acceptable, the exception handler should execute FNINIT and should probably terminate the task." What they seem to really mean is "If neither software system design constraint is acceptable, then your design is stupid and we can't reasonably be expected to support it." Oct 23, 2021 at 18:41
  • 1
    I am still wandering why it has to be a different exception just for this. What constraint stopped Intel from making this a GPF? Why would the FPU care if it's this specific case of an illegal memory access and not other cases?
    – DarkAtom
    Oct 23, 2021 at 18:51

2 Answers 2

3

The exception dates back to the 80286/80287. Intel 80286 CPU: Real Mode Emulation says that "The current case that cannot be restarted in general is any floating point operand reference where the second or subsequent word exceeded a segment limit. The exception 9 handler must execute FNINIT before any other WAIT or ESC instruction. The internal status of the 80287 cannot be read until it is forced idle by FNINIT. The FNINIT instruction will mark all floating point data registers as empty, set top of stack to 0, and mask all errors. The numeric instruction and data addresses stored in the 80287 will correctly point at the failing instruction. If the 80286 program interrupted by the math address error is not the program that executed the failed ESC instruction, then that program can be restarted."

3

The Intel 80386 CPU [...] use[d] either a 80287 or 80387 as an external FPU. When the x87 FPU accesses memory,

No, unlike the 8087 which was a real coprocessor, 287 and later are I/O devices. They do not make any memory access on their own. All access is handled by the 286/386.

the CPU makes the necessary privilege checks and generates an exception if the access is illegal (not enough privilege, page/segment not present etc.).

For what it can check.

On CPUs without an on-board FPU (like the 80386), the exception generated is the Coprocessor Segment Overrun Exception (interrupt 9), unlike all other memory accesses which use the GPF.

Because GPF can only be raised if the CPU knows ahead of time that an access will be problematic.

What I understand from this is that if the FPU makes an illegal memory access then its entire state cannot be recovered. Am I actually getting this right?

Yes ... err ... no. The FPU does not make any access, but the CPU does. For all instructions with a defined amount of data the CPU can check that data and do the usual int. But instructions like FSTENV handle an amount of data that is unknown to the CPU. Thus the CPU can only check the start address for that transfer beforehand. After that a DMA like transfer follows:

  1. The CPU prepares an address pointer using the address given in the instruction
  2. Check address for validity
  3. If not -> GPF
  4. CPU checks for BUSY active
  5. If BUSY is inactive, transfer is ended -> exit loop
  6. CPU takes a 16-bit (287) or 32-bit (387) word from the FPU's data port (800000FCh)
  7. CPU checks for address being a valid one
  8. If not -> Coprocessor Segment Overrun Exception
  9. CPU stores 16/32 bit word at the address pointer
  10. Address pointer gets incremented by 2/4 depending on FPU
  11. Go to step 4

(see here for a description of the interface)

What happens if the illegal access happened, for example, because the memory was swapped to disk? Normally the OS would load the missing page to memory, put it back in the virtual address space and continue execution. However, with this behavior, it is impossible to do that because ‘The failed numeric instruction is not restartable’. You cannot get the FPU state either, all that can be done is to FNINIT and kill the innocent application.

Exactly that. Although the innocence might be debatable.

The "DMA" protocol used does not allow any restart, as it does not include any signalling to tell the 287/387 to interrupt the ongoing transfer.

I also don't understand this statement very well: ‘The return address on the stack might not point to either the failed numeric instruction or the instruction following the failed numeric instruction’. Does this mean that the return address is undefined and all the OS can do is kill the application

Abort is the only solution.

Also, think of it, even if the address could be recovered, the only useful action would still be an abort as the FPU state after that is undefined. That means it's for all practical use corrupted, thus any continuation at that point is useless at best, harmful at most.

Of course, if OS and environment provide a user side abort handler, more sophisticated applications get a chance to restart from some save point.

17
  • One sentence ended with "or" ("... to interrupt the ongoing transfer or " ) I removed it in my edit but maybe you were planning on including something additional there?
    – Sep Roland
    Feb 12 at 20:55
  • Oh ... well, short term memory doesn't help, so let's just drop it :))
    – Raffzahn
    Feb 12 at 21:04
  • In fact, the documentation says the only option is a FNINIT. I wonder why can't other instructions like FNSAVE be used?
    – Yuhong Bao
    Feb 13 at 2:39
  • This also reminds me that the 80386 started checking the end of the operand address so page fault for example can be raised instead of this exception. I assume the 80286 only checked the beginning, right?
    – Yuhong Bao
    Feb 13 at 2:56
  • Of course I assume the 80386 began waiting until the data transfers complete before the next instruction, right?
    – Yuhong Bao
    Feb 13 at 3:19

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .