绍兴网站制作,网站开发者密钥,做网站路由器映射外网,包装设计网站有哪些文章目录 一、断点简介1.1 硬件断点1.2 软件断点 二、断点源码分析2.1 断点相关结构体2.1.1 struct breakpoint2.1.2 struct bp_location 2.2 断点源码简介2.3 break设置断点2.4 enable break2.5 disable breakpoint2.6 delete breakpoint2.7 info break 命令源码解析 三、Linu… 文章目录 一、断点简介1.1 硬件断点1.2 软件断点 二、断点源码分析2.1 断点相关结构体2.1.1 struct breakpoint2.1.2 struct bp_location 2.2 断点源码简介2.3 break设置断点2.4 enable break2.5 disable breakpoint2.6 delete breakpoint2.7 info break 命令源码解析 三、Linux int3源码简介3.1 do_int33.2 do_signal3.3 SIGTRAP信号处理 参考资料 一、断点简介
通常断点是程序中用户指定的位置如果程序执行到达该位置用户希望在该位置重新获得程序的控制权。
实现断点主要有两种方法硬件断点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位中的任何一位写入一个会导致异常#GP0。
在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.c2.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 dont 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 thisbptype. */struct breakpoint_ops
{/* Destructor. Releases everything from SELF (but not SELFitself). */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 juststarted). */void (*re_set) (struct breakpoint *self);/* Insert the breakpoint or watchpoint or activate the catchpoint.Return 0 for success, 1 if the breakpoint, watchpoint orcatchpoint type is not supported, -1 for failure. */int (*insert_location) (struct bp_location *);/* Remove the breakpoint/catchpoint that was previously insertedwith the insert method above. Return 0 for success, 1 if thebreakpoint, 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 hittingbreakpoint location BL. This function does not check if weshould stop, only if BL explains the stop. ASPACE is the addressspace in which the event occurred, BP_ADDR is the address atwhich the inferior stopped, and WS is the target_waitstatusdescribing 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 functioncreate_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 ordinarybreakpoints). However, there may be some special cases where we mightneed to do some tweaks, e.g., seestrace_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, ifavailable, will be called instead of performing the default actionfor this bp_loc_type. */struct bp_location_ops
{/* Destructor. Releases everything from SELF (but not SELFitself). */void (*dtor) (struct bp_location *self);
};struct bp_location
{/* Chain pointer to the next breakpoint location forthe 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-levelbreakpoint. This pointer is NULL iff this bp_location is nolonger attached to a breakpoint. For example, when a breakpointis deleted, its locations may still be found in themoribund_locations list, or if we had stopped for it, inbpstats. */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 thecurrent 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_FLAGand 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;elseops 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 1add_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 ofbreakpoints 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 thisbptype. */struct breakpoint_ops
{...../* This method will be responsible for creating a breakpoint given its SALs.Usually, it just calls create_breakpoints_sal (for ordinarybreakpoints). However, there may be some special cases where we mightneed to do some tweaks, e.g., seestrace_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 definedbreakpoints) so they once again become (or continue to be) effectivein 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);}elsemap_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 callsdisable_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);}elsemap_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 deleteinternal breakpoints, these have to be deleted with anexplicit 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;}//在所有线程中移除与断点相关的 bpstatiterate_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 breakpointswhose 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标志设置为1match 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:47info 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. IfFILTER is non-NULL, call it on each breakpoint and only include theones for which it returns non-zero. Return the total number ofbreakpoints 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 sizerequired 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);elsebkpttbl_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 */elseui_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 theallflag 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 theres a filter, let the caller decide how to reportempty list. */if (!filter){if (args NULL || *args \0)ui_out_message (uiout, 0, No breakpoints or watchpoints.\n);elseui_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 whenthere 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源码简介
3.1 do_int3
do_int3函数是内核中用于处理INT3中断的函数。当处理器执行到INT3指令时会触发一个断点异常并将控制权传递给这个函数。
// /arch/x86/include/asm/traps.h/* Interrupts/Exceptions */
enum {......X86_TRAP_BP, /* 3, Breakpoint */......
};// arch/x86/include/uapi/asm/signal.h#define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
#define SIGILL 4
#define SIGTRAP 5
......在Linux内核中SIGTRAP信号通常用于调试目的例如在断点命中或单步执行期间。
dotraplinkage void __kprobes notrace do_int3(struct pt_regs *regs, long error_code)
{enum ctx_state prev_state;......prev_state exception_enter();......//通知所有注册的异常通知处理函数发生了INT3中断if (notify_die(DIE_INT3, int3, regs, error_code, X86_TRAP_BP,SIGTRAP) NOTIFY_STOP)goto exit;/** Let others (NMI) know that the debug stack is in use* as we may switch to the interrupt stack.*/debug_stack_usage_inc();preempt_conditional_sti(regs);//执行陷阱处理将中断类型设置为X86_TRAP_BP断点异常信号类型设置为SIGTRAP跟踪信号并传递相关参数。do_trap(X86_TRAP_BP, SIGTRAP, int3, regs, error_code, NULL);preempt_conditional_cli(regs);debug_stack_usage_dec();
exit:exception_exit(prev_state);
}do_trap函数在do_int3函数中用于处理陷阱trap的过程。
static void __kprobes
do_trap(int trapnr, int signr, char *str, struct pt_regs *regs,long error_code, siginfo_t *info)
{//tsk变量指向当前进程的task_struct结构体struct task_struct *tsk current;//该函数负责检查是否应当处理陷阱并发送信号给进程if (!do_trap_no_signal(tsk, trapnr, str, regs, error_code))return;/** We want error_code and trap_nr set for userspace faults and* kernelspace faults which result in die(), but not* kernelspace faults which are fixed up. die() gives the* process no chance to handle the signal and notice the* kernel fault information, so that wont result in polluting* the information about previously queued, but not yet* delivered, faults. See also do_general_protection below.*/tsk-thread.error_code error_code;tsk-thread.trap_nr trapnr;......//将信号发送给进程if (info)force_sig_info(signr, info, tsk);else//对于do_int3是调用force_sig函数给当前进程发送信息signr SIGTRAPforce_sig(signr, tsk);
}do_trap函数在do_int3函数中用于处理陷阱trap的过程do_trap函数用来给当前进程发送SIGTRAP信号。
void
force_sig(int sig, struct task_struct *p)
{force_sig_info(sig, SEND_SIG_PRIV, p);
}force_sig的函数用于向指定的进程发送信号对于do_int3即给当前进程发送信号SIGTRAP。
SEND_SIG_PRIV宏在调用force_sig_info函数时该宏表示以特权方式发送信号即无需对目标进程的权限进行验证。
/** Force a signal that the process cant ignore: if necessary* we unblock the signal and change any SIG_IGN to SIG_DFL.** Note: If we unblock the signal, we always reset it to SIG_DFL,* since we do not want to have a signal handler that was blocked* be invoked when user space had explicitly blocked it.** We dont want to have recursive SIGSEGVs etc, for example,* that is why we also clear SIGNAL_UNKILLABLE.*/
int
force_sig_info(int sig, struct siginfo *info, struct task_struct *t)
{......//向目标进程发送信号并传递附加的信号信息ret specific_send_sig_info(sig, info, t);......return ret;
}force_sig_info函数用于强制向指定进程发送信号并确保进程无法忽略该信号。它会根据信号的处理动作和进程的阻塞状态来相应地修改处理动作和信号集合并向进程发送信号。这可以确保信号被进程及时处理而无法被忽略或阻塞。
do_trap函数在do_int3函数中用于处理陷阱trap的过程do_trap函数用来给当前进程发送SIGTRAP信号。
3.2 do_signal
当进程接收到SIGTRAP信号时内核会检查进程的信号处理行为。如果进程已经注册了一个信号处理程序信号处理函数来处理SIGTRAP信号内核将跳转到该处理程序执行相应的操作。
如果进程没有注册SIGTRAP信号处理程序或者注册了默认的处理程序内核会执行默认行为do_signal()。对于SIGTRAP信号默认行为是使进程进入调试模式。
当一个进程收到信号后在内核态会调用do_signal函数来处理对应的信号
/** Note that init is a special process: it doesnt get signals it doesnt* want to handle. Thus you cannot kill init even with a SIGKILL even by* mistake.*/
static void do_signal(struct pt_regs *regs)
{struct ksignal ksig;if (get_signal(ksig)) {/* Whee! Actually deliver the signal. */handle_signal(ksig, regs);return;}......
}1do_signal函数用于处理进程收到的信号。它首先尝试从进程的信号队列中获取一个待处理的信号。
2如果成功获取到一个信号通过调用get_signal函数则执行handle_signal函数来真正处理该信号。也就是将信号传递给信号处理程序信号处理函数进行处理。
do_signal函数用于处理进程收到的信号。如果有待处理的信号它将调用相应的信号处理程序来处理信号。
/** Eventually thatll replace get_signal_to_deliver(); macro for now,* to avoid nastiness with include order.*/
#define get_signal(ksig) \
({ \struct ksignal *p (ksig); \p-sig get_signal_to_deliver(p-info, p-ka, \signal_pt_regs(), NULL);\p-sig 0; \
})int get_signal_to_deliver(siginfo_t *info, struct k_sigaction *return_ka,struct pt_regs *regs, void *cookie)
{......signr dequeue_signal(current, current-blocked, info);if (!signr)break; /* will return 0 */if (unlikely(current-ptrace) signr ! SIGKILL) {signr ptrace_signal(signr, info);if (!signr)continue;}......
}static int ptrace_signal(int signr, siginfo_t *info)
{......//处理SIGTRAP信号ptrace_stop(signr, CLD_TRAPPED, 0, info);......
}// why CLD_TRAPPEDstatic void ptrace_stop(int exit_code, int why, int clear_code, siginfo_t *info)
{......//决定使用陷阱机制trapping进行调试。陷阱机制是一种用于控制和监视程序执行的调试机制。//表示在处理调试过程中确保在清除 TRAPPING 状态之前 TRACED 状态可见非常重要。//这样做是为了避免在 do_wait() 函数中出现潜在问题该函数负责处理 tracer 的等待行为。//set_current_state(TASK_TRACED);......//通知父进程do_notify_parent_cldstop(current, true, why);......//主动发起调度让出CPUfreezable_schedule();......
}ptrace_stop这个函数通常用于通知让调式器运行子进程处于被跟踪状态和停止状态。
其中set_current_state(TASK_TRACED)TRACED 表示被跟踪的进程状态表示该进程正在被调试。而 TRAPPING 表示进程被停止或被陷阱的状态。 该语句强调了在调试场景中确保 TRACED 状态可见后再清除 TRAPPING 状态的操作顺序的重要性。这个操作顺序的目的是防止跟踪器无法意识到进程已经转换为 TRACED 状态。如果在清除 TRAPPING 状态之前跟踪器没有意识到进程已经进入 TRACED 状态那么 do_wait() 函数可能无法正确处理等待过程。
// why CLD_TRAPPED/*** do_notify_parent_cldstop - notify parent of stopped/continued state change* tsk: task reporting the state change* for_ptracer: the notification is for ptracer* why: CLD_{CONTINUED|STOPPED|TRAPPED} to report** Notify tsks parent that the stopped/continued state has changed. If* for_ptracer is %false, tsks group leader notifies to its real parent.* If %true, tsk reports to tsk-parent which should be the ptracer.** CONTEXT:* Must be called with tasklist_lock at least read locked.*/
static void do_notify_parent_cldstop(struct task_struct *tsk,bool for_ptracer, int why)
{struct siginfo info;unsigned long flags;struct task_struct *parent;struct sighand_struct *sighand;cputime_t utime, stime;if (for_ptracer) {parent tsk-parent;} else {tsk tsk-group_leader;parent tsk-real_parent;}info.si_signo SIGCHLD;info.si_errno 0;/** see comment in do_notify_parent() about the following 4 lines*/rcu_read_lock();info.si_pid task_pid_nr_ns(tsk, task_active_pid_ns(parent));info.si_uid from_kuid_munged(task_cred_xxx(parent, user_ns), task_uid(tsk));rcu_read_unlock();task_cputime(tsk, utime, stime);info.si_utime cputime_to_clock_t(utime);info.si_stime cputime_to_clock_t(stime);//这里传递的参数 CLD_TRAPPEDinfo.si_code why;switch (why) {case CLD_CONTINUED:info.si_status SIGCONT;break;case CLD_STOPPED:info.si_status tsk-signal-group_exit_code 0x7f;break;case CLD_TRAPPED:info.si_status tsk-exit_code 0x7f;break;default:BUG();}sighand parent-sighand;spin_lock_irqsave(sighand-siglock, flags);if (sighand-action[SIGCHLD-1].sa.sa_handler ! SIG_IGN !(sighand-action[SIGCHLD-1].sa.sa_flags SA_NOCLDSTOP))__group_send_sig_info(SIGCHLD, info, parent);/** Even if SIGCHLD is not generated, we must wake up wait4 calls.*/__wake_up_parent(tsk, parent);spin_unlock_irqrestore(sighand-siglock, flags);
}这里传递的参数 why CLD_TRAPPED用于通知父进程有关停止 – CLD_TRAPPED的原因。 调用__group_send_sig_info函数向父进程发送SIGCHLD信号的相关信息。 调用__wake_up_parent函数唤醒等待父进程的wait系统调用。
3.3 SIGTRAP信号处理
当一个进程收到SIGTRAP信号后如果当前进程处于被跟踪状态即
struct task_struct {unsigned int ptrace;
}current-ptrace signr ! SIGKILL1设置其进程状态为TASK_TRACED
set_current_state(TASK_TRACED);2给父进程即调试器发送SIGCHLD信号
do_notify_parent_cldstop()父进程收到SIGCHLD信号就知道子进程处于停止和被跟踪状态了这样父进程就可以对子进程进行各种调试操作了。
SIGCHLD是一个由子进程发送给父进程的信号用于通知父进程子进程的状态变化。具体来说当一个子进程终止或停止时它会向父进程发送SIGCHLD信号。
这里是指一个子进程停止时会向父进程发送SIGCHLD信号。
3发出调度请求主动让出CPU
/* Now we dont run again until woken by SIGCONT or SIGKILL */
freezable_schedule();#ifdef CONFIG_FREEZER/** These macros are intended to be used whenever you want allow a sleeping* task to be frozen. Note that neither return any clear indication of* whether a freeze event happened while in this function.*//* Like schedule(), but should not block the freezer. */
#define freezable_schedule() \
({ \freezer_do_not_count(); \schedule(); \freezer_count(); \
})# cat /boot/config-3.10.0-1160.el7.x86_64 | grep CONFIG_FREEZER
CONFIG_FREEZERy这个宏的作用类似于schedule()函数但是在调用schedule()之前会调用freezer_do_not_count()函数来告诉冻结器不要计数当前任务。然后执行调度操作。在调度完成后会调用freezer_count()函数来重新计数当前任务。这样做的目的是允许任务在休眠期间被冻结但不会被计入冻结器的统计中。
参考资料
Intel vol3 Linux 3.10.0
GDB使用详解 https://sourceware.org/gdb/wiki/Internals