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.
1 Answer
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:
- No
INT1
is generated after executingpopf
. This is surprising, because the flow diagram 2-29 contains the step COMPLETE CURRENT INSTRUCTION before the branch on TF. After completingpopf
, theTF
is set, and the action of entering the single-step handler should be taken. - Also no
INT1
is generated after the subsequentmov ax, 1234h
instruction. So even if we assumeTF
in diagram 2-29 to mean the state ofTF
at the beginning of execution an instruction, we observe a further delay of one instruction. This is comparable to the effect thatSTI
also 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
IF
andTF
, 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
TF
on interrupt entry. - When an external interrupt returns with
IRET
, anotherINT1
is 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 0A1h
instruction; the vector points directly toIRET
), there is in fact noINT1
after theINT A1
returns beforedec cx
gets executed. The datasheet is accurate on this topic. - After clearing the
TF
by issuingPOPF
at the end of the fragment, a lastINT1
is triggered, with the return address pointing toDEC AX
. Again, this is an effect of delayed recognition of changes toTF
I 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
int 0A1h
(i.e.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 instructiondec cx
.
To expand on the second-to-last item: I observed the following pattern:
mov bl, 1
is executed- the single-step handler is entered, with the return address pointing to the subsequent instruction
inc ax
- 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
inc ax
. - (the timer interrupt handler is executed without further single-step invocations)
inc ax
is 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.