GDB 源码分析 -- 断点源码解析

news2025/1/16 21:01:35

文章目录

  • 一、断点简介
    • 1.1 硬件断点
    • 1.2 软件断点
  • 二、断点源码分析
    • 2.1 断点相关结构体
      • 2.1.1 struct breakpoint
      • 2.1.2 struct bp_location
    • 2.2 断点源码简介
    • 2.3 break设置断点
    • 2.4 enable break
    • 2.5 disable breakpoint
    • 2.6 delete breakpoint
    • 2.7 info break 命令源码解析
  • 三、Linux int3源码简介
  • 参考资料

一、断点简介

通常,断点是程序中用户指定的位置,如果程序执行到达该位置,用户希望在该位置重新获得程序的控制权。

实现断点主要有两种方法:硬件断点(hardware breakpoints)或者软件断点(software breakpoints)。

enum bptype
  {
    bp_none = 0,		/* Eventpoint has been deleted */
    bp_breakpoint,		/* Normal breakpoint */  -- 软件断点
    bp_hardware_breakpoint,	/* Hardware assisted breakpoint */  -- 硬件断点

  }

1.1 硬件断点

硬件断点有时可以作为一些芯片的内置调试功能使用。通常,这些工作是通过具有专用寄存器来实现的,断点地址可以存储在该寄存器中。如果PC(程序计数器的缩写)与断点寄存器中的值匹配,CPU将引发异常并将其报告给GDB。

(1)硬件断点有时可以作为一些芯片的内置调试功能使用。通常,这些工作是通过具有专用寄存器来实现的,断点地址可以存储在该寄存器中。如果PC(程序计数器的缩写)与断点寄存器中的值匹配,CPU将引发异常并将其报告给GDB。

(2)另一种可能性是当仿真器(emulator)正在使用时;许多仿真器都包含一些电路,用于监视来自处理器的地址行,并在地址与断点地址匹配时强制停止。

(3)第三种可能性是,目标已经有能力以某种方式执行断点;例如,ROM监视器可以执行其自己的软件断点。因此,尽管这些不是字面上的“硬件断点”,但从GDB的角度来看,它们的工作原理是相同的;GDB不需要做任何事情,只需要设置断点并等待某件事发生。

由于它们依赖于硬件资源,硬件断点的数量可能有限;当用户要求设置更多的断点时,GDB将开始尝试设置软件断点。(在某些体系结构上,GDB无法知道是否有足够的硬件资源来插入所有硬件断点和观察点。)

对于x86_64:
处理器中的内部调试设施由一组8个调试寄存器(DR0-DR7)控制。MOV指令允许将设置数据加载到这些寄存器并从中存储。

在支持英特尔64体系结构的处理器上,调试寄存器DR0-DR7为64位。在32位模式和兼容模式下,对调试寄存器的写入会用零填充高32位。读取返回低32位。在64位模式中,DR6-DR7的高32位是保留的,必须用零写入。向高32位中的任何一位写入一个会导致异常#GP(0)。

在64位模式下,MOV DRn指令读取或写入调试寄存器的所有64位(忽略操作数大小前缀)。DR0-DR3的所有64位都可由软件写入。然而,MOV DRn指令不会检查写入DR0-DR3的地址是否在实现的限制范围内。只有处理器实现生成的有效地址才支持地址匹配。

对于x86_64,通常使用 DR0-DR3 来作为断点地址寄存器,断点地址寄存器(DR0 到 DR3)是用来指定最多 4 个断点的地址。

在调试器和处理器的调试功能中,断点地址寄存器用于设置断点,即在指定的地址处中断执行,并触发调试器中断处理程序。这种机制允许程序的调试者在特定的代码位置设置断点,以便在程序执行到该位置时中断执行,以便进行调试操作。

断点地址寄存器通常由调试器使用,它们是特定于处理器架构的调试寄存器。不同的处理器架构可能具有不同数量和命名方式的断点地址寄存器。一般情况下,常见的处理器架构支持的断点地址寄存器是 DR0、DR1、DR2 和 DR3。

调试器可以将断点地址加载到这些寄存器中,然后在程序执行期间监视相应的地址。当执行到设置的断点地址时,处理器会触发中断或异常,调试器可以捕获该中断或异常,并执行相应的调试操作,如暂停执行、收集调试信息等。
在这里插入图片描述

1.2 软件断点

软件断点需要GDB做更多的工作。基本理论是,GDB将用陷阱(trap)、非法除法或其他会导致异常的指令替换程序指令,然后当遇到异常时,GDB会采取异常并停止程序。当用户说要继续时,GDB将恢复原始指令,单步执行,重新插入陷阱,然后继续。

此外,软件断点指令应该是最小的指令大小,这样它就不会覆盖可能成为跳转目标的指令,并且当程序跳到断点指令的中间时会导致灾难。(严格来说,断点必须不大于可能成为跳转目标的指令之间的最小间隔;也许存在一种只有偶数指令可以跳转到的体系结构。)请注意,指令集可能没有任何可用于软件断点的指令,尽管在实践中只有ARC未能定义这样的指令。

对于x86_64来说软件断点:
软件断点通常用 int3指令来实现:
在这里插入图片描述
int3指令 只有一个字节 0xcc。

INT3指令生成一个特殊的单字节操作码(CC),用于调用调试异常处理程序。(这种单字节形式很有价值,因为它可以用来用断点替换任何指令的第一个字节,包括其他单字节指令,而不会重写其他代码)。为了进一步支持其作为调试断点的功能,用CC操作码生成的中断也不同于常规软件中断。

Breakpoint instruction (INT3)

中断指令(INT3)是生成断点异常(#BP)的指令,生成断点异常(#BP),该异常将程序控制转移到调试器过程或任务。此指令是设置指令断点的另一种方法。当需要四个以上的断点,或者在源代码中放置断点时,它尤其有用。

中断指令(INT3)是一条软件中断指令,用于在程序执行到指定位置时产生中断。它的操作码为0xCC。当处理器执行到这条指令时,会触发一个断点异常,即中断向量号为3的异常(#BP)。这会导致处理器暂停当前的执行流程,并将控制权转移给调试器。

调试器可以在程序中适当的位置插入中断指令(INT3)作为断点。当程序执行到这个断点时,处理器会触发中断异常,并将控制权传递给调试器。调试器可以在中断处理程序中执行相应的调试操作,如暂停程序、收集调试信息、修改寄存器值等。

中断指令(INT3)常用于软件调试和动态调试过程中。它提供了一种灵活的方式来设置断点,并允许调试器以软件的形式与被调试的程序进行交互。相比于硬件断点,中断指令的优点是可以设置更多的断点,并且可以直接在源代码中插入断点,方便调试源代码级别的问题。然而,由于中断指令是软件实现的,相比硬件断点可能会引入一些额外的执行开销。

二、断点源码分析

基本的断点对象处理在:

/gdb-7.6.1/gdb/breakpoint.c

更多的断点操作都在:

/gdb-7.6.1/gdb/infrun.c

2.1 断点相关结构体

GDB维护关于每个断点的两种类型的信息(或观察点或其他相关事件)。

第一种类型对应 struct breakpoint ;这是一个相对高级的结构,它包含源位置、停止条件、在遇到断点时要执行的用户命令等等。

第二种类型的信息对应于 struct bp_location 。每个断点都有一个或(最终)多个与其相关联的位置,这些位置表示用于停止程序的特定于目标和特定于机器的机制。例如,一个观察点表达式可能需要多个硬件观察点,以便捕捉被观察表达式值的所有变化。

2.1.1 struct breakpoint

struct breakpoint
  {
    /* Methods associated with this breakpoint.  */
    const struct breakpoint_ops *ops;

    struct breakpoint *next;
    /* Type of breakpoint.  */
    enum bptype type;
    /* Zero means disabled; remember the info but don't break here.  */
    enum enable_state enable_state;
    /* What to do with this breakpoint after we hit it.  */
    enum bpdisp disposition;
    /* Number assigned to distinguish breakpoints.  */
    int number;

    /* Location(s) associated with this high-level breakpoint.  */
    struct bp_location *loc;

	......
	
  };

这些字段提供了关于断点的各种属性和配置信息,用于管理和控制断点的行为。

/* This structure is a collection of function pointers that, if available,
   will be called instead of the performing the default action for this
   bptype.  */

struct breakpoint_ops
{
  /* Destructor.  Releases everything from SELF (but not SELF
     itself).  */
  void (*dtor) (struct breakpoint *self);

  /* Allocate a location for this breakpoint.  */
  struct bp_location * (*allocate_location) (struct breakpoint *);

  /* Reevaluate a breakpoint.  This is necessary after symbols change
     (e.g., an executable or DSO was loaded, or the inferior just
     started).  */
  void (*re_set) (struct breakpoint *self);

  /* Insert the breakpoint or watchpoint or activate the catchpoint.
     Return 0 for success, 1 if the breakpoint, watchpoint or
     catchpoint type is not supported, -1 for failure.  */
  int (*insert_location) (struct bp_location *);

  /* Remove the breakpoint/catchpoint that was previously inserted
     with the "insert" method above.  Return 0 for success, 1 if the
     breakpoint, watchpoint or catchpoint type is not supported,
     -1 for failure.  */
  int (*remove_location) (struct bp_location *);

  /* Return true if it the target has stopped due to hitting
     breakpoint location BL.  This function does not check if we
     should stop, only if BL explains the stop.  ASPACE is the address
     space in which the event occurred, BP_ADDR is the address at
     which the inferior stopped, and WS is the target_waitstatus
     describing the event.  */
  int (*breakpoint_hit) (const struct bp_location *bl,
			 struct address_space *aspace,
			 CORE_ADDR bp_addr,
			 const struct target_waitstatus *ws);

 	......
 	
  /* Create SALs from address string, storing the result in linespec_result.

     For an explanation about the arguments, see the function
     `create_sals_from_address_default'.

     This function is called inside `create_breakpoint'.  */
  void (*create_sals_from_address) (char **, struct linespec_result *,
				    enum bptype, char *, char **);

  /* This method will be responsible for creating a breakpoint given its SALs.
     Usually, it just calls `create_breakpoints_sal' (for ordinary
     breakpoints).  However, there may be some special cases where we might
     need to do some tweaks, e.g., see
     `strace_marker_create_breakpoints_sal'.

     This function is called inside `create_breakpoint'.  */
  void (*create_breakpoints_sal) (struct gdbarch *,
				  struct linespec_result *,
				  struct linespec_sals *, char *,
				  char *,
				  enum bptype, enum bpdisp, int, int,
				  int, const struct breakpoint_ops *,
				  int, int, int, unsigned);

  	......
};

struct breakpoint_ops结构中的这些函数指针为调试环境中与断点相关的操作提供了各种定制和扩展点。它们允许对断点行为进行精细控制,并提供了实现自定义功能的机会。

2.1.2 struct bp_location

enum bp_loc_type
{
  bp_loc_software_breakpoint,
  bp_loc_hardware_breakpoint,
  bp_loc_hardware_watchpoint,
  bp_loc_other			/* Miscellaneous...  */
};

/* This structure is a collection of function pointers that, if
   available, will be called instead of performing the default action
   for this bp_loc_type.  */

struct bp_location_ops
{
  /* Destructor.  Releases everything from SELF (but not SELF
     itself).  */
  void (*dtor) (struct bp_location *self);
};

struct bp_location
{
  /* Chain pointer to the next breakpoint location for
     the same parent breakpoint.  */
  struct bp_location *next;

  /* Methods associated with this location.  */
  const struct bp_location_ops *ops;

  /* The reference count.  */
  int refc;

  /* Type of this breakpoint location.  */
  enum bp_loc_type loc_type;

  /* Each breakpoint location must belong to exactly one higher-level
     breakpoint.  This pointer is NULL iff this bp_location is no
     longer attached to a breakpoint.  For example, when a breakpoint
     is deleted, its locations may still be found in the
     moribund_locations list, or if we had stopped for it, in
     bpstats.  */
  struct breakpoint *owner;

  ......
};

2.2 断点源码简介

/* Chains of all breakpoints defined.  */

struct breakpoint *breakpoint_chain;

定义了一个名为 breakpoint_chain 的全局变量,用于存储所有已定义的断点的链表。

struct breakpoint 是一个结构体类型,用于表示一个断点的信息。通过将断点的结构体按照链表的形式连接起来,可以方便地管理和遍历所有已定义的断点。

例如,当设置一个断点时,会创建一个新的 struct breakpoint 对象,并将其添加到 breakpoint_chain 的链表中。当删除断点时,可以在链表中找到相应的断点,并将其从链表中移除。

遍历断点:

/* Walk the following statement or block through all breakpoints.
   ALL_BREAKPOINTS_SAFE does so even if the statement deletes the
   current breakpoint.  */

#define ALL_BREAKPOINTS(B)  for (B = breakpoint_chain; B; B = B->next)

宏定义,用于遍历断点链表中的所有断点。使用 breakpoint_chain 全局变量作为链表的头,并通过 B = B->next 将当前断点的下一个断点赋值给 B,实现链表的遍历。

2.3 break设置断点

(gdb) break main
Breakpoint 1 at 0x400883: file 1.c, line 58.
(gdb) break addBook
Breakpoint 2 at 0x400620: file 1.c, line 18.
(gdb) break removeBook
Breakpoint 3 at 0x4006b5: file 1.c, line 29.
(gdb) break printBooks
Breakpoint 4 at 0x4007de: file 1.c, line 47.
void
break_command (char *arg, int from_tty)
{
  break_command_1 (arg, 0, from_tty);
}
/* Set a breakpoint.
   ARG is a string describing breakpoint address,
   condition, and thread.
   FLAG specifies if a breakpoint is hardware on,
   and if breakpoint is temporary, using BP_HARDWARE_FLAG
   and BP_TEMPFLAG.  */

static void
break_command_1 (char *arg, int flag, int from_tty)
{
  int tempflag = flag & BP_TEMPFLAG;
  enum bptype type_wanted = (flag & BP_HARDWAREFLAG
			     ? bp_hardware_breakpoint
			     : bp_breakpoint);
  struct breakpoint_ops *ops;
  const char *arg_cp = arg;

  /* Matching breakpoints on probes.  */
  if (arg && probe_linespec_to_ops (&arg_cp) != NULL)
    ops = &bkpt_probe_breakpoint_ops;
  else
    ops = &bkpt_breakpoint_ops;

  create_breakpoint (get_current_arch (),
		     arg,
		     NULL, 0, NULL, 1 /* parse arg */,
		     tempflag, type_wanted,
		     0 /* Ignore count */,
		     pending_break_support,
		     ops,
		     from_tty,
		     1 /* enabled */,
		     0 /* internal */,
		     0);
}

break_command_1 函数用于设置断点(breakpoint)。

(1)函数的参数包括 arg、flag 和 from_tty。arg 是描述断点地址、条件和线程的字符串。flag 是一个控制标志,指示断点是否是硬件断点和临时断点。from_tty 是一个标志,表示函数是否从终端调用。
(2)函数内部首先根据 flag 的值确定要设置的断点的类型,是硬件断点还是普通断点。
(3)然后根据 arg 的值判断是否与探测(probe)相关的断点,如果是,则使用 bkpt_probe_breakpoint_ops,否则使用 bkpt_breakpoint_ops。
(4)接下来调用 create_breakpoint 函数来创建断点。这个函数将断点的信息传递给 create_breakpoint,包括当前架构、断点地址、解析参数的标志、断点类型、计数器等。
(5)最后,函数调用 create_breakpoint 来创建断点,并传递相关参数:

get_current_arch() 获取当前的架构信息。
NULL 表示没有附加操作。
0 表示没有指定断点长度。
NULL 表示没有条件。
1 表示需要解析 arg 参数。
tempflag 表示断点是否是临时断点。
type_wanted 表示断点的类型。
0 表示忽略计数器。
pending_break_support 表示断点的支持情况。
ops 表示断点操作。
from_tty 表示函数是否从终端调用。
1 表示启用断点。
0 表示断点不是内部断点。
//Set a breakpoint.  This function is shared between CLI and MI functions for setting a breakpoint.
create_breakpoint()
	-->ops->create_breakpoints_sal()
	
	-->install_breakpoint()
		-->add_to_breakpoint_chain()

ops->create_breakpoints_sal()用于创建实际的断点。

static const unsigned char x86_breakpoint[] = { 0xCC };
#define x86_breakpoint_len 1

add_to_breakpoint_chain 用于添加断点到全局断点链表的末尾:

/* Add breakpoint B at the end of the global breakpoint chain.  */

static void
add_to_breakpoint_chain (struct breakpoint *b)
{
  struct breakpoint *b1;

  /* Add this breakpoint to the end of the chain so that a list of
     breakpoints will come out in order of increasing numbers.  */

  b1 = breakpoint_chain;
  if (b1 == 0)
    breakpoint_chain = b;
  else
    {
      while (b1->next)
	b1 = b1->next;
      b1->next = b;
    }
}

用于将一个断点(struct breakpoint 对象)添加到全局断点链表的末尾。

该函数首先检查全局断点链表 breakpoint_chain 是否为空。如果为空,将断点 b 直接设置为链表的头节点。否则,遍历链表找到最后一个节点,并将断点 b 添加为该节点的下一个节点。

其中 struct breakpoint_ops :

/* This structure is a collection of function pointers that, if available,
   will be called instead of the performing the default action for this
   bptype.  */

struct breakpoint_ops
{
	.....

  /* This method will be responsible for creating a breakpoint given its SALs.
     Usually, it just calls `create_breakpoints_sal' (for ordinary
     breakpoints).  However, there may be some special cases where we might
     need to do some tweaks, e.g., see
     `strace_marker_create_breakpoints_sal'.

     This function is called inside `create_breakpoint'.  */
  void (*create_breakpoints_sal) (struct gdbarch *,
				  struct linespec_result *,
				  struct linespec_sals *, char *,
				  char *,
				  enum bptype, enum bpdisp, int, int,
				  int, const struct breakpoint_ops *,
				  int, int, int, unsigned);

	......
};

2.4 enable break

enable 断点编号,启用某个被禁用的断点:
有两种用法:
enable 1:启动编号为1的断点。
enable:启动所有定义的断点。

在这里插入图片描述

/* The enable command enables the specified breakpoints (or all defined
   breakpoints) so they once again become (or continue to be) effective
   in stopping the inferior.  */

static void
enable_command (char *args, int from_tty)
{
  if (args == 0)
    {
      struct breakpoint *bpt;

	//启用所有已定义的断点
      ALL_BREAKPOINTS (bpt)
	if (user_breakpoint_p (bpt))
	  enable_breakpoint (bpt);
    }
  else if (strchr (args, '.'))
    {
      //则根据提供的断点编号找到对应的断点位置
      struct bp_location *loc = find_location_by_number (args);
      if (loc)
	{
	  if (!loc->enabled)
	    {
	      //将enabled标志设置为1,表示启用断点位置
	      loc->enabled = 1;
	      //调用mark_breakpoint_location_modified函数标记断点位置已修改。
	      mark_breakpoint_location_modified (loc);
	    }
	  if (target_supports_enable_disable_tracepoint ()
	      && current_trace_status ()->running && loc->owner
	      && is_tracepoint (loc->owner))
	    target_enable_tracepoint (loc);
	}
	  //调用update_global_location_list函数更新全局位置列表
      update_global_location_list (1);
    }
  else
    map_breakpoint_numbers (args, do_map_enable_breakpoint, NULL);
}

enable_command的函数,它用于启用指定的断点或所有已定义的断点,使它们能够再次有效地停止被调试程序。

函数接受两个参数:args和from_tty,其中args是一个字符串,包含了用户提供的参数信息,from_tty是一个整数,表示函数是否从终端调用。

函数的功能如下:
(1)如果args为0(即未指定参数),则遍历所有断点,并对用户定义的断点进行启用操作,即调用enable_breakpoint函数使断点生效。
(2)如果args包含字符’.',则根据提供的断点编号找到对应的断点位置(bp_location),如果位置存在,则进行以下操作:

如果该位置的enabled标志为0(表示未启用),则将enabled标志设置为1,表示启用断点位置。
调用mark_breakpoint_location_modified函数标记断点位置已修改。
如果当前追踪状态为运行状态且位置的owner存在且为追踪点(tracepoint),则调用target_enable_tracepoint函数启用追踪点。

(3)最后,调用update_global_location_list函数更新全局位置列表。

总之,该函数根据用户提供的参数启用指定的断点,或者如果未提供参数,则启用所有已定义的断点。

启用所有已定义的断点:

enable_breakpoint()
	-->enable_breakpoint_disp()
static void
enable_breakpoint_disp (struct breakpoint *bpt, enum bpdisp disposition,
			int count)
{
  int target_resources_ok;

  if (bpt->type == bp_hardware_breakpoint)
    {
      int i;
      i = hw_breakpoint_used_count ();
      target_resources_ok = 
	target_can_use_hardware_watchpoint (bp_hardware_breakpoint, 
					    i + 1, 0);
      if (target_resources_ok == 0)
	error (_("No hardware breakpoint support in the target."));
      else if (target_resources_ok < 0)
	error (_("Hardware breakpoints used exceeds limit."));
    }

  if (is_watchpoint (bpt))
    {
      /* Initialize it just to avoid a GCC false warning.  */
      enum enable_state orig_enable_state = 0;
      volatile struct gdb_exception e;

      TRY_CATCH (e, RETURN_MASK_ALL)
	{
	  struct watchpoint *w = (struct watchpoint *) bpt;

	  orig_enable_state = bpt->enable_state;
	  bpt->enable_state = bp_enabled;
	  update_watchpoint (w, 1 /* reparse */);
	}
      if (e.reason < 0)
	{
	  bpt->enable_state = orig_enable_state;
	  exception_fprintf (gdb_stderr, e, _("Cannot enable watchpoint %d: "),
			     bpt->number);
	  return;
	}
    }

  if (bpt->enable_state != bp_permanent)
    bpt->enable_state = bp_enabled;

  bpt->enable_state = bp_enabled;

  /* Mark breakpoint locations modified.  */
  mark_breakpoint_modified (bpt);

  if (target_supports_enable_disable_tracepoint ()
      && current_trace_status ()->running && is_tracepoint (bpt))
    {
      struct bp_location *location;

      for (location = bpt->loc; location; location = location->next)
	target_enable_tracepoint (location);
    }

  bpt->disposition = disposition;
  bpt->enable_count = count;
  update_global_location_list (1);

  observer_notify_breakpoint_modified (bpt);
}

2.5 disable breakpoint

disable 断点编号,禁用某个断点,使得断点不会被触发;
disable 1:禁用编号为1的断点。
disable :禁用所有定义的断点。
在这里插入图片描述

/* A callback for map_breakpoint_numbers that calls
   disable_breakpoint.  */

static void
do_map_disable_breakpoint (struct breakpoint *b, void *ignore)
{
  iterate_over_related_breakpoints (b, do_disable_breakpoint, NULL);
}

static void
disable_command (char *args, int from_tty)
{
  if (args == 0)
    {
      struct breakpoint *bpt;

	//遍历所有断点,并对用户定义的断点进行禁用操作
      ALL_BREAKPOINTS (bpt)
	if (user_breakpoint_p (bpt))
	  disable_breakpoint (bpt);
    }
  else if (strchr (args, '.'))
    {
      //提供的断点编号找到对应的断点位置
      struct bp_location *loc = find_location_by_number (args);
      if (loc)
	{
	  if (loc->enabled)
	    {
	      //将enabled标志设置为0,表示禁用断点位置
	      loc->enabled = 0;
	      //调用mark_breakpoint_location_modified函数标记断点位置已修改
	      mark_breakpoint_location_modified (loc);
	    }
	  if (target_supports_enable_disable_tracepoint ()
	      && current_trace_status ()->running && loc->owner
	      && is_tracepoint (loc->owner))
	    target_disable_tracepoint (loc);
	}
	  //调用update_global_location_list函数更新全局位置列表
      update_global_location_list (0);
    }
  else
    map_breakpoint_numbers (args, do_map_disable_breakpoint, NULL);
}

disable_command的函数,用于禁用指定的断点或所有已定义的断点,使它们不再生效。

函数接受两个参数:args和from_tty,其中args是一个字符串,包含了用户提供的参数信息,from_tty是一个整数,表示函数是否从终端调用。

函数的功能如下:
(1)如果args为0(即未指定参数),则遍历所有断点,并对用户定义的断点进行禁用操作,即调用disable_breakpoint函数使断点失效。
(2)如果args包含字符’.',则根据提供的断点编号找到对应的断点位置(bp_location),如果位置存在,则进行以下操作:

如果该位置的enabled标志为1(表示已启用),则将enabled标志设置为0,表示禁用断点位置。
调用mark_breakpoint_location_modified函数标记断点位置已修改。
如果当前追踪状态为运行状态且位置的owner存在且为追踪点(tracepoint),则调用target_disable_tracepoint函数禁用追踪点。

(3)最后,调用update_global_location_list函数更新全局位置列表。

该函数根据用户提供的参数禁用指定的断点,或者如果未提供参数,则禁用所有已定义的断点。

2.6 delete breakpoint

delete 断点编号,删除某个断点:
delete 1:删除编号为1的断点
delete :删除所有断点
在这里插入图片描述

void
delete_command (char *arg, int from_tty)
{
  struct breakpoint *b, *b_tmp;

  dont_repeat ();

  //如果未提供参数
  if (arg == 0)
    {
      int breaks_to_delete = 0;

      /* Delete all breakpoints if no argument.  Do not delete
         internal breakpoints, these have to be deleted with an
         explicit breakpoint number argument.  */
      ALL_BREAKPOINTS (b)
	if (user_breakpoint_p (b))
	  {
	    breaks_to_delete = 1;
	    break;
	  }

	  //如果未提供参数,则删除所有用户定义的断点
	  //在删除之前,函数会进行用户确认
      /* Ask user only if there are some breakpoints to delete.  */
      if (!from_tty
	  || (breaks_to_delete && query (_("Delete all breakpoints? "))))
	{
	  ALL_BREAKPOINTS_SAFE (b, b_tmp)
	    if (user_breakpoint_p (b))
	      delete_breakpoint (b);
	}
    }
  else
    //如果提供了参数,函数根据用户提供的参数删除指定的断点
    map_breakpoint_numbers (arg, do_map_delete_breakpoint, NULL);
}

delete_command的函数,用于删除指定的断点或所有断点。

函数接受两个参数:arg和from_tty,其中arg是一个字符串,包含了用户提供的参数信息,from_tty是一个整数,表示函数是否从终端调用。

函数的功能如下:
(1)如果arg为0(即未指定参数),则进行以下操作:

定义一个整数变量breaks_to_delete,用于记录是否存在可删除的断点。
遍历所有断点,并检查是否存在用户定义的断点。如果存在,则将breaks_to_delete设置为1并跳出循环。
如果from_tty为假或者(存在可删除的断点且用户确认要删除所有断点),则执行以下操作:
使用ALL_BREAKPOINTS_SAFE宏遍历所有断点,并在遍历过程中安全删除用户定义的断点。

(2)否则,如果提供了参数,则调用map_breakpoint_numbers函数,将断点编号映射到一个回调函数do_map_delete_breakpoint,并传递NULL作为额外的参数。

该函数根据用户提供的参数删除指定的断点,或者如果未提供参数,则删除所有用户定义的断点。在删除之前,函数会进行用户确认。

删除所有用户定义的断点调用函数delete_breakpoint :

void
delete_breakpoint (struct breakpoint *bpt)
{
	......
  if (breakpoint_chain == bpt)
    breakpoint_chain = bpt->next;

  ALL_BREAKPOINTS (b)
    if (b->next == bpt)
    {
      //删除断点,断点bpt前一个断点的 next 指向 断点bpt的下一个断点
      b->next = bpt->next;
      break;
    }
    
  //在所有线程中移除与断点相关的 bpstat
  iterate_over_threads (bpstat_remove_breakpoint_callback, bpt);

  //现在该断点已从断点列表中删除,请更新全局位置列表。这将删除以前属于此断点的位置。
  //在释放断点之前执行此操作,因为remove_breakpoint查看位置的所有者。
  //位置完全独立可能是更好的设计,但现在情况并非如此。
  update_global_location_list (0);

  //调用断点的析构函数进行清理,并释放断点的内存。
  bpt->ops->dtor (bpt);

  bpt->type = bp_none;
  xfree (bpt);
}

函数从断点链表中删除断点 bpt,然后在所有线程中移除与断点相关的 bpstat。随后,更新全局位置列表,以移除与已删除断点相关的位置。最后,调用断点的析构函数进行清理,并释放断点的内存。

删除指定的断点,调用do_map_delete_breakpoint函数:

/* Call FUNCTION on each of the breakpoints
   whose numbers are given in ARGS.  */

static void
map_breakpoint_numbers (char *args, void (*function) (struct breakpoint *,
						      void *),
			void *data)
{
  int num;
  struct breakpoint *b, *tmp;
  int match;
  struct get_number_or_range_state state;

  if (args == 0)
    error_no_arg (_("one or more breakpoint numbers"));

  //解析断点编号字符串
  init_number_or_range (&state, args);

  while (!state.finished)
    {
      char *p = state.string;

      match = 0;

	  //从state中获取一个断点编号(通过调用get_number_or_range函数),并将其存储在num变量中。
      num = get_number_or_range (&state);
      if (num == 0)
	{
	  warning (_("bad breakpoint number at or near '%s'"), p);
	}
      else
	{
	  //使用ALL_BREAKPOINTS_SAFE宏遍历所有断点
	  ALL_BREAKPOINTS_SAFE (b, tmp)
	    //并检查每个断点的编号是否与目标编号匹配
	    if (b->number == num)
	      {
	    //找到匹配的断点,将match标志设置为1
		match = 1;
		//调用传入的function函数,并将该断点和data作为参数传递进去
		function (b, data);
		break;
	      }
	  if (match == 0)
	    printf_unfiltered (_("No breakpoint number %d.\n"), num);
	}
    }
}

为map_breakpoint_numbers的函数,用于在给定的断点编号中对每个断点调用指定的函数。

函数接受三个参数:args、function和data。args是一个字符串,包含了断点的编号信息;function是一个函数指针,指向要对每个断点调用的函数;data是一个指针,可以传递给函数作为额外的数据。

函数的功能如下:
(1)首先,检查args是否为0(即未提供参数),如果是,则抛出一个错误,提示需要至少提供一个断点编号。
(2)初始化一个get_number_or_range_state结构体,用于解析断点编号字符串。
(3)进入循环,直到解析完所有的断点编号
(4)在循环的每次迭代中,从state中获取一个断点编号(通过调用get_number_or_range函数),并将其存储在num变量中。
(5)如果获取的断点编号为0,表示解析失败或格式错误,发出警告提示。
(6)否则,使用ALL_BREAKPOINTS_SAFE宏遍历所有断点,并检查每个断点的编号是否与目标编号匹配。

如果找到匹配的断点,将match标志设置为1,然后调用传入的function函数,并将该断点和data作为参数传递进去。
如果没有匹配的断点,打印消息提示找不到对应的断点编号。

(7)循环继续,直到解析完所有的断点编号。

该函数用于在给定的断点编号中进行遍历,并对每个断点调用指定的函数。它通过解析参数字符串来确定要处理的断点编号,并在找到匹配的断点时调用指定的函数,同时提供额外的数据作为参数。

在上述调用传入的function函数do_map_delete_breakpoint:

do_map_delete_breakpoint()
	-->iterate_over_related_breakpoints (b, do_delete_breakpoint, NULL)
		-->delete_breakpoint()

删除指定断定也是调用delete_breakpoint函数。

2.7 info break 命令源码解析

(gdb) info break
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000400883 in main at 1.c:58
2       breakpoint     keep y   0x0000000000400620 in addBook at 1.c:18
3       breakpoint     keep y   0x00000000004006b5 in removeBook at 1.c:29
4       breakpoint     keep y   0x00000000004007de in printBooks at 1.c:47

info break 也可简写为 i b。

static void
breakpoints_info (char *args, int from_tty)
{
  breakpoint_1 (args, 0, NULL);

  default_collect_info ();
}
/* Print information on user settable breakpoint (watchpoint, etc)
   number BNUM.  If BNUM is -1 print all user-settable breakpoints.
   If ALLFLAG is non-zero, include non-user-settable breakpoints.  If
   FILTER is non-NULL, call it on each breakpoint and only include the
   ones for which it returns non-zero.  Return the total number of
   breakpoints listed.  */

static int
breakpoint_1 (char *args, int allflag, 
	      int (*filter) (const struct breakpoint *))
{
  struct breakpoint *b;
  struct bp_location *last_loc = NULL;
  int nr_printable_breakpoints;
  struct cleanup *bkpttbl_chain;
  struct value_print_options opts;
  int print_address_bits = 0;
  int print_type_col_width = 14;
  struct ui_out *uiout = current_uiout;

  get_user_print_options (&opts);

  /* Compute the number of rows in the table, as well as the size
     required for address fields.  */
  nr_printable_breakpoints = 0;
  ALL_BREAKPOINTS (b)
    {
      /* If we have a filter, only list the breakpoints it accepts.  */
      if (filter && !filter (b))
	continue;

      /* If we have an "args" string, it is a list of breakpoints to 
	 accept.  Skip the others.  */
      if (args != NULL && *args != '\0')
	{
	  if (allflag && parse_and_eval_long (args) != b->number)
	    continue;
	  if (!allflag && !number_is_in_list (args, b->number))
	    continue;
	}

      if (allflag || user_breakpoint_p (b))
	{
	  int addr_bit, type_len;

	  addr_bit = breakpoint_address_bits (b);
	  if (addr_bit > print_address_bits)
	    print_address_bits = addr_bit;

	  type_len = strlen (bptype_string (b->type));
	  if (type_len > print_type_col_width)
	    print_type_col_width = type_len;

	  nr_printable_breakpoints++;
	}
    }

  if (opts.addressprint)
    bkpttbl_chain 
      = make_cleanup_ui_out_table_begin_end (uiout, 6,
					     nr_printable_breakpoints,
                                             "BreakpointTable");
  else
    bkpttbl_chain 
      = make_cleanup_ui_out_table_begin_end (uiout, 5,
					     nr_printable_breakpoints,
                                             "BreakpointTable");

  if (nr_printable_breakpoints > 0)
    annotate_breakpoints_headers ();
  if (nr_printable_breakpoints > 0)
    annotate_field (0);
  ui_out_table_header (uiout, 7, ui_left, "number", "Num");	/* 1 */
  if (nr_printable_breakpoints > 0)
    annotate_field (1);
  ui_out_table_header (uiout, print_type_col_width, ui_left,
		       "type", "Type");				/* 2 */
  if (nr_printable_breakpoints > 0)
    annotate_field (2);
  ui_out_table_header (uiout, 4, ui_left, "disp", "Disp");	/* 3 */
  if (nr_printable_breakpoints > 0)
    annotate_field (3);
  ui_out_table_header (uiout, 3, ui_left, "enabled", "Enb");	/* 4 */
  if (opts.addressprint)
    {
      if (nr_printable_breakpoints > 0)
	annotate_field (4);
      if (print_address_bits <= 32)
	ui_out_table_header (uiout, 10, ui_left, 
			     "addr", "Address");		/* 5 */
      else
	ui_out_table_header (uiout, 18, ui_left, 
			     "addr", "Address");		/* 5 */
    }
  if (nr_printable_breakpoints > 0)
    annotate_field (5);
  ui_out_table_header (uiout, 40, ui_noalign, "what", "What");	/* 6 */
  ui_out_table_body (uiout);
  if (nr_printable_breakpoints > 0)
    annotate_breakpoints_table ();

  ALL_BREAKPOINTS (b)
    {
      QUIT;
      /* If we have a filter, only list the breakpoints it accepts.  */
      if (filter && !filter (b))
	continue;

      /* If we have an "args" string, it is a list of breakpoints to 
	 accept.  Skip the others.  */

      if (args != NULL && *args != '\0')
	{
	  if (allflag)	/* maintenance info breakpoint */
	    {
	      if (parse_and_eval_long (args) != b->number)
		continue;
	    }
	  else		/* all others */
	    {
	      if (!number_is_in_list (args, b->number))
		continue;
	    }
	}
      /* We only print out user settable breakpoints unless the
	 allflag is set.  */
      if (allflag || user_breakpoint_p (b))
	print_one_breakpoint (b, &last_loc, allflag);
    }

  do_cleanups (bkpttbl_chain);

  if (nr_printable_breakpoints == 0)
    {
      /* If there's a filter, let the caller decide how to report
	 empty list.  */
      if (!filter)
	{
	  if (args == NULL || *args == '\0')
	    ui_out_message (uiout, 0, "No breakpoints or watchpoints.\n");
	  else
	    ui_out_message (uiout, 0, 
			    "No breakpoint or watchpoint matching '%s'.\n",
			    args);
	}
    }
  else
    {
      if (last_loc && !server_command)
	set_next_address (last_loc->gdbarch, last_loc->address);
    }

  /* FIXME?  Should this be moved up so that it is only called when
     there have been breakpoints? */
  annotate_breakpoints_table_end ();

  return nr_printable_breakpoints;
}

代码中的struct breakpoint和struct bp_location是用于表示断点和断点位置的结构体。断点结构体可能包含有关断点的信息,如地址、类型等。断点位置结构体可能包含有关断点所在的文件、行号等信息。

代码中的struct value_print_options结构体用于存储值的打印选项,它可能包含有关如何打印值的信息,例如格式、精度等。

通过调用get_user_print_options函数,该函数可能用于获取用户定义的值的打印选项。

代码使用宏和循环迭代所有的断点。对于每个断点,它执行一系列的检查和计算,以确定是否满足打印条件,并根据需要打印断点信息。

使用过滤器函数可以根据特定条件筛选断点。如果提供了过滤器函数,并且对于某个断点返回false,那么该断点将被跳过。

代码还检查了args字符串,如果提供了该字符串并且与断点的编号匹配,那么该断点将被打印出来。这可能用于根据断点编号来过滤特定的断点。

代码根据断点的类型和其他条件计算打印选项,例如地址位数和断点类型列的宽度。

使用uiout对象和相关函数,代码构建了一个断点表格,以便将符合条件的断点信息以表格形式打印出来。表格的列可能包括断点编号、地址、类型等。

在打印断点之前,代码会使用annotate_breakpoints_headers函数为表格添加表头,使用ui_out_table_header函数设置不同列的表头。

使用ui_out_table_body函数开始表格正文,并使用annotate_breakpoints_table函数为表格添加断点行。

对于满足条件的每个断点,代码调用print_one_breakpoint函数打印断点信息。

在打印完所有断点后,使用do_cleanups函数进行清理操作,以结束断点表格的构建。

如果没有符合条件的可打印断点,代码可能会显示一条相应的消息,以向用户指示当前没有可用的断点。

最后,代码根据需要设置下一个断点地址,并使用annotate_breakpoints_table_end函数注释断点表格的结束部分。

三、Linux int3源码简介

参考资料

Intel vol3

GDB使用详解
https://sourceware.org/gdb/wiki/Internals

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/944258.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

我想开通期权?如何开通期权账户?

场内期权的合约由交易所统一标准化定制&#xff0c;大家面对的同一个合约对应的价格都是一致的&#xff0c;比较公开透明&#xff0c;期权开户当天不能交易的&#xff0c;期权开户需要满足20日日均50万及半年交易经验即可操作&#xff0c;下文科普我想开通期权&#xff1f;如何…

软件测试的CMA和CNAS分别是什么?有什么用途和区别?

各行各业都有不同的证书&#xff0c;第三方软件检测机构也需要经过考核检验以获取认可。今天我们将围绕软件测试的CMA和CNAS展开讨论&#xff0c;以帮助您更好地了解它们的定义、区别和用途。 一、CMA软件测试&#xff1a; 1、定义&#xff1a;CMA软件测试是指基于中国计量认…

maven部署

一、下载Maven 地址&#xff1a;Maven – Download Apache Maven 二、解压缩&#xff0c;设置环境变量 tar -xvf apache-maven-3.8.8-bin.tar.gz export MAVEN_HOME/opt/apache-maven-3.8.8 export PATH$MAVEN_HOME/bin:$PATH echo $MAVEN_HOME echo $PATH mvn -v

从零开始的Hadoop学习(四)| SSH无密登录配置、集群配置

1. SSH 无密登录配置 1.1 配置 ssh &#xff08;1&#xff09;基本语法 ssh 另一台电脑的IP地址 &#xff08;2&#xff09;ssh 连接时出现 Host key verification failed 的解决方法 [atguiguhadoop102 ~]$ ssh hadoop103&#xff08;3&#xff09;回退到 hadoop102 [at…

linux离线安装rdbtools,需先安装python

离线安装python3 下载python包&#xff0c;下载地址&#xff1a;https://www.python.org/ftp/python/ 我选的是https://www.python.org/ftp/python/3.9.0/Python-3.9.0.tgz 将文件上传至linux服务器&#xff0c;解压 tar -xf Python-3.9.0.tgz cd Python-3.9.0 mkdir /usr/l…

SpringBoot v2.7.x+ 整合Swagger3入坑记?

目录 一、依赖 二、集成Swagger Java Config 三、配置完毕 四、解决方案 彩蛋 想尝鲜&#xff0c;坑也多&#xff0c;一起入个坑~ 一、依赖 SpringBoot版本&#xff1a;2.7.14 Swagger版本&#xff1a;3.0.0 <dependency><groupId>com.github.xiaoymin<…

Java面试之斐波那契数列(Fibonacci)及其应用:青蛙跳台阶问题

文章目录 一、斐波那契数列问题1.1 题目1.2 什么是斐波那契数列1.3 效率很低的解法&#xff1a;递归1.4 递归缺点分析 二、比较好的解决办法2.1 保存数列中间项2.2 从下往上计算 三、公式法四、青蛙跳台阶问题4.1 题目及分析4.2 代码实现 一、斐波那契数列问题 1.1 题目 写一…

制作鲜花商城小程序的详细步骤

如果你是一个新手商家&#xff0c;想要进入鲜花团购市场&#xff0c;但是不知道如何制作一个小程序商城&#xff0c;那么这篇文章就是为你准备的。以下是制作鲜花团购小程序商城的详细步骤&#xff1a; 1. 登录乔拓云平台后台&#xff0c;进入商城管理页面 首先&#xff0c;你需…

Shopee测评补单技巧:优化商品流量与权重

在Shopee平台上进行测评是一种低成本、高回报的推广方式&#xff0c;可以对商品的流量、转化率、质量分和权重等多个指标起到辅助作用。以下是一些Shopee测评的技巧和注意事项&#xff1a; 1. 测评原理与周期&#xff1a; Shopee的新品周期为7天&#xff0c;平台会在这段时间…

【管理运筹学】第 6 章 | 运输问题(4,表上作业法 |闭回路调整法以及特殊情况 | 产销不平衡的运输问题)

文章目录 引言二、表上作业法2.3 改进的方法 —— 闭回路调整法2.4 表上作业法中的特殊情况&#xff08;一&#xff09;无穷多最优解&#xff08;二&#xff09;退化 三、产销不平衡的运输问题3.1 产量大于销量3.2 销量大于产量 写在最后 引言 接下来我们学习表上作业法的最后…

图论算法基础:单源最短路径Dijkstra算法分析

文章目录 图的邻接矩阵 一.Dijkstra算法分析算法的核心逻辑要素算法的执行逻辑 二.Dijkstra算法接口实现邻接矩阵堆优化版本: 图的邻接矩阵 namespace Graph_Structure {//Vertex是代表顶点的数据类型,Weight是边的权值的数据类型,MAX_W是权值的上限值(表示不相两)//Direction…

项目 - 后端技术栈转型方案

前言 某开发项目的后端技术栈比较老了&#xff0c;现在想换到新的技术栈上。使用更好的模式、设计思想、更合理的架构等&#xff0c;为未来的需求迭代做铺垫。怎么办呢&#xff1f;假设系统目前在线上运行着的&#xff0c;直接整体换的话耗时太久&#xff0c;且中间还有新的需…

【嵌入式】Keil5自带JLink识别不到芯片(unkown to this version of the jlink software)的处理

目录 一 问题现象 二 原因分析 三 问题处理 一 问题现象 使用了一款新的嵌入式芯片&#xff0c;灵动微MM32SPIN27PF&#xff0c;安装了官方提供的J-Link Pack支持包。 【1】直接使用 JLink_V694a 可以正常烧写程序&#xff1b; 【2】使用Keil5烧写失败&#xff0c;显示报错“…

demo的改进和完善(首页添加介绍和图片)

1.在首页添加介绍 将布局→ 区域→ 静态内容拖拽到body中→ 标题 更改为自己想要的名称→ 源 写入html代码 《学习demo》是用来记录我的学习过程中遇到的问题及解决方案&#xff0c;见证了我的进步和成长 <hr> 相关内容可见<a href"https://blog.csdn.net/clove…

你的住宅安全吗?这个技能赶紧学学

随着城市化的不断加速和人口增长&#xff0c;住宅小区的管理和安全问题也愈发凸显出来。在这种背景下&#xff0c;门禁监控系统成为了一种既有效又实用的解决方案。 门禁监控系统不仅可以控制和管理出入小区的人员和车辆&#xff0c;还可以提供实时监控和记录&#xff0c;为小区…

视频监控/视频汇聚/视频云存储EasyCVR平台HLS流集成在小程序无法播放的问题排查

安防视频/视频云存储/视频集中存储EasyCVR视频监控综合管理平台可以根据不同的场景需求&#xff0c;让平台在内网、专网、VPN、广域网、互联网等各种环境下进行音视频的采集、接入与多端分发。在视频能力上&#xff0c;视频云存储平台EasyCVR可实现视频实时直播、云端录像、视频…

实现 Trie (前缀树)

题目链接 实现 Trie (前缀树) 题目描述 注意点 word 和 prefix 仅由小写英文字母组成 解答思路 首先要理解前缀树是什么&#xff0c;参照该篇文章【图解算法】模板变式——带你彻底搞懂字典树(Trie树)在了解前缀树是什么后&#xff0c;设计前缀树就会更加容易&#xff0c;…

java八股文面试[多线程]——主内存和工作内存的关系

JAVA内存模型&#xff08;JMM&#xff09;共享变量&#xff1a;如果一个变量在多个线程的工作内存中都存在副本&#xff0c;那么这个变量就是这几个线程的共享变量。 上面的工作内存其实是java内存模型抽象出来的概念&#xff0c;下面简要介绍一下java内存模型&#xff08;JMM&…

正中优配:股票经手费必须交吗?

在股票出资中&#xff0c;经手费是一个不可避免的要素。那么&#xff0c;股票经手费有必要交吗&#xff1f;从多个视点来看&#xff0c;这个问题需求进行必定的剖析。 法令视点&#xff1a;股票经手费有必要交 从法令视点来看&#xff0c;股票经手费有必要交。依据《证券法》的…

SQL 语句继续学习之记录三

一&#xff0c;数据的插入&#xff08;insert 语句的使用方法&#xff09; 使用insert语句可以向表中插入数据(行)。原则上&#xff0c;insert语句每次执行一行数据的插入。 列名和值用逗号隔开&#xff0c;分别扩在&#xff08;&#xff09;内&#xff0c;这种形式称为清单。…