Why Single Stepping Instruction on X86?

后端 未结 5 1961
太阳男子
太阳男子 2021-01-01 00:03

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 wh

相关标签:
5条回答
  • 2021-01-01 00:15

    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.

    0 讨论(0)
  • 2021-01-01 00:24

    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.

    0 讨论(0)
  • 2021-01-01 00:34

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

    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).

    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.

    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.

    With debug exception only, you will not be able to break at a location you want to. Only after you break at some location, you can then configure the processor for single-step or other things, which are consumed by the debug exception.

    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 the two-step break-and-then-debug mechanism is necessary.

    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

    0 讨论(0)
  • 2021-01-01 00:37

    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.

    0 讨论(0)
  • 2021-01-01 00:40

    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.

    0 讨论(0)
提交回复
热议问题