The 1979 version of the 8086 family user's manual is available at different places in the internet, see 1,2,3. It seems there is no newer version available. This manual documents the single-stepping feature in figure 2-29 on page 2-23 and figure 2-31 on page 2-27. Are these diagrams accurate? The emphasis of this question is in the corner cases like concurrent request of single stepping, external interrupts and internal interrupts.
It seems the diagrams are not accurate. I wrote a test program that traces
INT1 invocations (and delays execution during
INT1 to increase the chance of getting hit by timer interrupts) while executing the following machine language fragment:
pushf mov ax, 300h ; 100h = TF; 200h = IF push ax popf ; This instruction sets the trace flag mov ax, 1234h mov bl, 1 inc ax mov dx, ds mov es, ax ; On 8086/8088: No interrupts before NOP is executed nop mov dx, ss mov ss, dx ; On any x86 processor: No interrupts before NOP is executed nop int 0A1h dec cx popf ; This instruction clears the trace flag dec ax
The results of executing this fragments on an 8088-1 clocked at 10 MHz are:
INT1is generated after executing
popf. This is surprising, because the flow diagram 2-29 contains the step COMPLETE CURRENT INSTRUCTION before the branch on TF. After completing
TFis set, and the action of entering the single-step handler should be taken.
- Also no
INT1is generated after the subsequent
mov ax, 1234hinstruction. So even if we assume
TFin diagram 2-29 to mean the state of
TFat the beginning of execution an instruction, we observe a further delay of one instruction. This is comparable to the effect that
STIalso enable interrupts to be recognized only after the subsequent instruction (the manual explicitly says so on page 2-48).
- If an external interrupt (e.g. a timer interrupt) is recognized, the 8088 triggers a single-step interrupt after pushing the state to the stack and clearing
TF, just as the datasheet shows in diagram 2-29 and explains on page 2-28: "If the processor is single-stepping, it processes an interrupt (either internal or external) as follows. Control is passed normally (flags, CS and IP are pushed) to the procedure designated to handle the type of interrupt that has occurred. However, before the first instruction of that procedure is executed, the single-step interrupt is "recognized" and control is passed normally (flags, CS and IP are pushed) to the type 1 interrupt procedure".
- During execution of the interrupt handlers, no single-stepping occurrs, because the processor clears
TFon interrupt entry.
- When an external interrupt returns with
INT1is triggered before the next instruction of the interrupted program is executed. This seems to contradict Figure 2-31. The flow diagram shows the recognition of the single step condition before the first IRQ handler instruction is executed, but on return of the IRQ handler it goes straight into EXECUTE NEXT INSTRUCTION without recognizing the single step interrupt.
- On the other hand, if an internal interrupt is caused by executing an instruction (like the
int 0A1hinstruction; the vector points directly to
IRET), there is in fact no
INT A1returns before
dec cxgets executed. The datasheet is accurate on this topic.
- After clearing the
POPFat the end of the fragment, a last
INT1is triggered, with the return address pointing to
DEC AX. Again, this is an effect of delayed recognition of changes to
TFI could not find in the data sheet.
- If an external interrupt is requested during execution of the single-step handler, the external interrupt is entered after returning from the single-step handler before executing the next instruction.
- Even though the instruction following
dec cx) is not seen as return address of a single-step handler invocation, an external interrupt can be recognized at that point, and one might see a single-step invocation on the first instruction of the timer interrupt, with the return address of the timer interrupt pointing to the instruction
To expand on the second-to-last item: I observed the following pattern:
mov bl, 1is executed
- the single-step handler is entered, with the return address pointing to the subsequent instruction
- the single-step handler is entered again, with the return address pointing to the first instruction of the timer interrupt handler. The return address of the timer interrupt handler is still the instruction
- (the timer interrupt handler is executed without further single-step invocations)
inc axis executed
- the single-step handler is entered, with the return address pointing to the instruction
mov dx, ds
This behaviour means that you can not rely on the return instruction of the single-step handler being executed before the single-step handler might be entered again. This detail tripped me in my 286 emulator attempt.
This behaviour also means that if the combined process of the single-step handler and the timer interrupt handler is slow enough that it takes longer than a time tick, the processor might be spinning in timer interrupt executions (with a single-step invocation on the first instruction each) without any instructions of the interrupted code getting executed inbetween. I verified this by adding a 50 millisecond delay into the single-step code with standard PC/DOS timer period of 55ms, and observed a burst of 6 single-step handler invocations, all of them pointing to the entry point of the timer interrupt, without any change of the timer interrupt return adderss.