8

So there is "int 3" which is an interrupt instruction used for breakpoints in debuggers.

But then there is also "int 1" which is used for single stepping. But why is this needed? I've read that setting the Trap Flag (TF) in EFLAGS register will enable single stepping and will trap into the OS for each instruction. So why is a separate interrupt type needed?

Thanks!

Timoteo
  • 259
  • 3
  • 10

5 Answers5

12

int 3 is a special 1-byte interrupt. Invoking it will break into the debugger if one is present, otherwise the application will typically crash.

When the debugger sets the trap flag, this causes the processor to automatically execute an int 1 interrupt after every instruction. This allows the debugger to single-step by instructions, without having to insert an int 3 instruction. You do not have to invoke this interrupt explicitly.

Neil
  • 54,642
  • 8
  • 60
  • 72
8

You are confusing the INT and INT 3 instructions with the interrupt vectors through which those instructions would call if the instruction were invoked. There is no single-step instruction.

The INT 3 (or "breakpoint instruction") will call the debugger if it is present (or rather, the debugger will hook the INT 3 vector so that when an INT 3 happens, the debugger will be called).

If the debugger sets the TF (trace flag), then every instruction will cause the #1 interrupt to occur. This will cause whatever address is in that interrupt vector to be called. Hopefully, this will be the single-step routine of a debugger. Eventually, the debugger will clear the TF, causing single-step interrupts to cease.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
  • 3
    Actually, int 3 should classify as a separate instruction. Since it's a single byte (0xCC) instead of the two byte INT instruction (0xCD). – Timoteo Oct 29 '11 at 22:34
3

int 3 is used to set a breakpoint so that the code can execute freely until a particular point (the breakpoint) is reached. this speeds up the debugging process so it is not necessary to trap through known good code.

int 1 is used to unconditionally halt after every instruction. this can be handy when conditional branch instructions are executed and the condition of the status flags is not known. otherwise, a breakpoint would need to be set at branch address and the address of the instruction following the branch.

int 1 is can also be used at board bring-up when both the board hardware and the bring-up are new and untested.

jim
  • 31
  • 1
  • It looks to me like the only new thing this adds that isn't already in the other answers is the last paragraph. That last para would work well as a suggested-edit to on of the other answers. (yes, that's acceptable from new users, when there is actually something useful or interesting to add.) – Peter Cordes Mar 23 '16 at 10:25
3

Others have already explained the distinction between the interrupt vector 1 and int 3 instruction.

Now, if you wonder why there're multiple interrupt vectors involved in handling of debug interrupts, I think it's just because the original 8086/8088 circuitry was intended to be relatively simple and to execute relatively simple software. It had very few special interrupt vectors and the int vector 1 was only used for the single-step trap and distinguishing it from the breakpoint trap was trivial, by the interrupt vector number, that is, it was sufficient to just have distinct handlers for the vector 1 and 3. That design was carried over to the x86 CPUs that followed. The newer CPUs substantially and "quickly" extended the set of the special interrupt vectors up to about 20 to handle new exceptions and extended the debugging functionality adding several other useful interrupt vector 1 triggers on top of the original single-step trap (e.g. instruction fetch, memory/port I/O, task switch, etc). It was logical to house most of them under the same interrupt vector since they're related and not consume more vectors.

Alexey Frunze
  • 61,140
  • 12
  • 83
  • 180
1

Below are some quote from the Intel Software Developer Manual Vol. 3B, Chapter 17:

Intel 64 and IA-32 architectures provide debug facilities for use in debugging code and monitoring performance.

The Intel 64 and IA-32 architectures dedicate two interrupt vectors to handling debug exceptions: vector 1 (debug exception, #DB) and vector 3 (breakpoint exception, #BP).

Both Debug Exception (#DB) and Breakpoint Instruction (triggeres #BP) are among such facilities. And both of them are meant for:

...transfers program control to a debug procedure or task...

Debug Exception (#DB) relies on the several debug registers (DR0~DR7).

While the breakpoint instruction (#BP) is:

... an alternative way to set code breakpoints....especially useful when more than four breakpoints are desired, or when breakpoints are being placed in the source code.

The breakpoint instruction doesn't have as much power as the debug exception which is backed by DR0~DR7. Breakpoint instruction cannot be placed in ROM code. But and debug exception can be generated for ROM code as long as the DR0-DR7 are properly configured.

For the debug exception:

The debug-exception handler is usually a debugger program or part of a larger software system. The processor generates a debug exception for any of several conditions. The debugger checks flags in the DR6 and DR7 registers to determine which condition caused the exception and which other conditions might apply.

The conditions that may trigger a debug-exception can be many. Such as by setting EFLAGS[TF] the single-step debug-exception will be triggered. By checking the DR6 (debug status register) and configuring the DR7 (debug control register), a Debug Exception handler can get the details of how a debug exception is triggered.

For the breakpoint exception:

The breakpoint exception (interrupt 3) is caused by execution of an INT 3 instruction. Debuggers use breakpoint exceptions ... as a mechanism for suspending program execution to examine registers and memory locations.

With the Intel386 and later IA-32 processors, it is more convenient to set breakpoints with the breakpoint-address registers (DR0 through DR3). However, the breakpoint exception still is useful for breakpointing debuggers, because a breakpoint exception can call a separate exception handler. The breakpoint exception is also useful when it is necessary to set more breakpoints than there are debug registers or when breakpoints are being placed in the source code of a program under development.

So we can see, breakpoint exception enables you to suspend the program execution, while the debug exception checks for several conditions and treat them differently.

Only after you break at some location, you can then configure the processor for single-step or other things.

You can configure the debug registers to specify the condition (such as code, memory or I/O locations, etc.) you want to break into, which will trigger #DB.

Or place the breakpoint instruction in the code where you want to break into, which will trigger #BP.

INT 3 is a single-byte op-code. So it can over-write any existing instruction with controllable side-effect to break into the execution of current program flow. Without it, how could you have the chance to set the single-step flag in EFLAGS at an appropriate time with no side-effect?

So it is a two-step break-and-then-debug mechanism.

The whole flow is:

First, wire a debugger as the handler to both int 1(#DB) and int 3(#BP).

Then put int3 to where you want to break in. Then debugger has the chance to step in.

Once debugger starts to handle the int3 (#BP), if you want single-stepping, tell the debugger to set the Trap Flag (TF) in EFLAGS. Then CPU will generate a int 1 (#DB) after each single instruction. Since debugger is also wired to int 1 (#DB), it will have a chance to step in, too.

ADD 1 - 5:55 PM 5/31/2019

(I discussed with one of my friends about how debugger works. He wrote a debugger before.)

It seems the INT 3 (#BP) is the most important one. You can explicitly place an INT 3 instruction at the location you want to break into. Or you can let the debugger to do that for you.

Once the INT 3 is hit, CPU will save the context of the broken program and switch to the INT 3 handler, which is usually part of the debugger. Now, the broken program is suspended because the execution is in the exception #3 handler now. The debugger is just a normal Windows or whatever desktop application. It can use the normal desktop message-loop to wait for user's commands to decide how to treat the program being debugged. So it seems both the debugee and the debugger are waiting now. But the reasons are very different.

Then programmer (the human) can instruct the debugger (the software) to examine the saved context of the debugee. Or just restore debugee's saved context and let it resume. Or it may set the TF flag in EFLAGS so that a #DB will be generated by the processor after each instruction.

But often, users may not want single-stepping at the instruction level. They may want to debug at the C statements level, which can be composed of many instructions. So the debugger can use the debug information, such as the PDB file, to find the location info. If users want to single-step at the C statement level, the debugger can find the beginning instruction of next C statement and rewrite the 1st byte of it with an INT 3. And then everything starts all over again.

It's just a delicate cooperation between human, the debugger software and the processor.

ADD 2 - 5:24 PM 10/14/2019

A related thread: Strange memory content display in Visual Studio debug mode

smwikipedia
  • 61,609
  • 92
  • 309
  • 482
  • *Only after you break at some location*. Or you could set hardware breakpoints before the target program begins execution. (But after you load it into memory, so you know some target addresses.) – Peter Cordes Apr 23 '19 at 10:55
  • @PeterCordes Yes indeed. Thanks for completing the story. :) – smwikipedia Apr 24 '19 at 01:52
  • 1
    When 8086 was first designed, "normally" code was written in assembly language, so single-stepping by instruction was the norm when debugging. Compiler devs debugging their compiler's output might also single-step instructions. (Or of course users trying to understand or reverse-engineer some compiler-generated code, or a random binary.) So IMO "normally" is too strong a word. Single-stepping machine code is perfectly normal even on a modern x86. I think "often" would be a good word choice for the point you're making. – Peter Cordes May 31 '19 at 10:44
  • 1
    And yes this is a good point that setting a new `int3` breakpoint and continuing can be better than single-stepping until reaching the next source line. In a user-space debugger under a modern OS, it requires another system call to modify the text of the target process, so for only a couple instructions it might be more efficient to just single-step `n` times for `n < 3` or so. And BTW, many C statements like `i++;` or `foo += bar;` or `if(foo)` compile to 1 or 2 instructions. "*are* composed of many instructions" is an overstatement. "Can be" is accurate. – Peter Cordes May 31 '19 at 10:49
  • @PeterCordes Refined. Thanks. – smwikipedia May 31 '19 at 11:00