处理器如何知道断点?

时间:2017-05-17 21:32:03

标签: c debugging exception assembly operating-system

让我们考虑一下这个非常简单的程序:

#include<stdio.h>

int main () 
{
    int num1=4, num2=5;
    printf("Welcome\n");
    printf("num1 + num2 = %d\n", num1+num2);
    return 0;
}

使用gcc -S prog.c查看生成的汇编代码时:

    .file   "p.c"
    .def    ___main;    .scl    2;  .type   32; .endef
    .section .rdata,"dr"
LC0:
    .ascii "Welcome\0"
LC1:
    .ascii "num1 + num2 = %d\12\0"
    .text
    .globl  _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
LFB10:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    $32, %esp
    call    ___main
    movl    $4, 28(%esp)
    movl    $5, 24(%esp)
    movl    $LC0, (%esp)
    call    _puts
    movl    28(%esp), %edx
    movl    24(%esp), %eax
    addl    %edx, %eax
    movl    %eax, 4(%esp)
    movl    $LC1, (%esp)
    call    _printf
    call    _getchar
    movl    $0, %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
LFE10:
    .ident  "GCC: (GNU) 5.3.0"
    .def    _puts;  .scl    2;  .type   32; .endef
    .def    _printf;    .scl    2;  .type   32; .endef
    .def    _getchar;   .scl    2;  .type   32; .endef

我知道CPU会看到编译器为它生成的汇编代码,我不明白的是程序如何停止在用户设置的breakpoint?为什么CPU没有继续运行程序?这是怎么回事? 我的意思是,为什么在取指令后它会停止?

我对它有点困惑,是Code :: Blocks照顾这个或者用户正在使用的程序吗?

提前致谢!

1 个答案:

答案 0 :(得分:10)

大多数现代指令集都包含breakpoint异常,用于允许调试器通过使用特殊软件中断指令临时替换相关程序指令,在程序代码中插入断点。在x86 / x86-64 ISA上,该指令是&#34;中断向量3&#34; (又名int3),通常作为单字节指令0xcc发出。

关于断点指令需要注意的一点是,它们通常必须至少与ISA上最小的指令一样小。这有几个原因。一些ISA需要最少的指令对齐;较短的指令通常具有较不严格的对齐要求。此外,用较长的指令替换某些指令意味着您可能会覆盖以后的指令。这在单线程应用程序中可能不是什么大问题,但在多线程应用程序中,它是一个显示阻塞。例如,考虑如果将可选分支末尾的短指令替换为较长的指令,而另一个正在运行的线程跳过该分支,可能会发生什么。

在其他情况下,可能不存在这样的特殊指令。在缺少特定断点指令的硬件平台上,有时会提供特殊的硬件寄存器,以使处理器在尝试访问存储器中的特定位置时进行陷阱。这些寄存器的数量通常相当有限,因此在使用多个断点进行调试时,专用断点指令非常有用。

当您在调试器中启动程序并添加启用软件的断点时,通常会发生以下情况:

调试器将程序加载到内存中并为您提供一些输入提示。您告诉调试器添加断点。它可能会使用一些信息来确定内存中断点实际上与程序的内存中表示形式相对应的位置。然后调试器解码该地址的指令(因为它通常想要替换整个指令)并替换 它(在内存中)使用断点指令。然后告诉调试器执行/继续执行程序。

当处理器遇到此指令时,它会生成陷阱。此陷阱作为中断传递给操作系统,该操作系统注意到陷阱用于调试程序。操作系统知道正在执行哪个程序(因此也正在执行它) - 因此它可以进行一些权限检查,以确保此时实际上允许用户调试应用程序。如果一切看起来都不错,操作系统会通知调试器遇到断点,并告诉您它已停止。

这不是一个普遍的解释。要实现上述要求,需要大量的OS支持。在Linux和BSD上,大多数此功能都是通过ptrace(2)系统调用公开的(它允许读取和替换指令,以及单步执行)。在符合POSIX标准的情况下,OS X不实现ptrace(2),而是为此提供各种Mach端口。 Windows完全有其他东西。

在嵌入式系统上,可能会提供特殊的硬件端口(如JTAG)以允许在硬件级别进行内省,从而允许开发外部调试器,这可以说是&#34;说话&#34;使用JTAG直接连接到硬件。