用 python 扩展 GDB
.gdbinit
文件中实现自定义命令 mv
代码如下
define mv
if $argc == 2
delete $arg0
# 注意新创建的断点编号和被删除断点的编号不同
break $arg1
else
print "输入参数数目不对,help mv 以获得用法"
end
end
# (gdb) help mv 会输出以下帮助文档
document mv
Move breakpoint.
Usage: mv old_breakpoint_num new_breakpoint
Example:
(gdb) mv 1 binary_search -- move breakpoint 1 to `b binary_search`
end
用 python
实现代码如下
# move.py
# 1. 导入 gdb 模块来访问 gdb 提供的 python 接口
import gdb
# 2. 用户自定义命令需要继承自 gdb.Command 类
class Move(gdb.Command):
# 3. docstring 里面的文本是不是很眼熟?gdb 会提取该类的__doc__属性作为对应命令的文档
"""Move breakpoint
Usage: mv old_breakpoint_num new_breakpoint
Example:
(gdb) mv 1 binary_search -- move breakpoint 1 to `b binary_search`
"""
def __init__(self):
# 4. 在构造函数中注册该命令的名字
super(self.__class__, self).__init__("mv", gdb.COMMAND_USER)
# 5. 在 invoke 方法中实现该自定义命令具体的功能
# args 表示该命令后面所衔接的参数,这里通过 string_to_argv 转换成数组
def invoke(self, args, from_tty):
argv = gdb.string_to_argv(args)
if len(argv) != 2:
raise gdb.GdbError('输入参数数目不对,help mv 以获得用法')
# 6. 使用 gdb.execute 来执行具体的命令
gdb.execute('delete' + argv[0])
gdb.execute('break' + argv[1])
# 7. 向 gdb 会话注册该自定义命令
Move()
.gdbinit
文件中导入 python
文件
layout src
# 导入 python 文件
source test.py
...
...
这样 gdb 每次启动时都会替我们 source 一下
gdb 的 python 接口
gdb 通过 gdb 模块提供了不少 python 接口。其中最为常用的是 gdb.execute
和 gdb.parse_and_eval
。
-
gdb.execute 可用于执行一个 gdb 命令。默认情况下,结果会输出到 gdb 界面上。如果想把输出结果转存到字符串中,设置 to_string 为 True:gdb.execute(cmd, to_string=True)。
-
gdb.parse_and_eval 接受一个字符串作为表达式,并以 gdb.Value 的形式返回表达式求值的结果。举例说,gdb 当前上下文中有一个变量 i,i 等于 3。那么 gdb.parse_and_eval(‘i + 1’) 的结果是一个 gdb.Value 的实例,其 value 属性的值为 4。这跟 (gdb) i + 1 是等价的。
何为 gdb.Value?在 gdb 会话里,我们可以访问 C/C++ 类型的值。当我们通过 python 接口跟这些值打交道时,gdb 会把它们包装成一个 gdb.Value 对象。
举个例子,struct Point 有 x 跟 y 两个成员。现在假设当前上下文中有一个 Point 类型的变量 point 和指向该变量的 Point 指针 p,就意味着:
point = gdb.parse_and_eval('point')
point['x'] # 等价于point.x
point['y'] # 等价于point.y
point.referenced_value() # 等价于&point
p = gdb.parse_and_eval('p')
point2 = p.dereference() # 等价于*p
point2['x'] # 等价于(*p).x,也即p->x
示例
C 代码
#include <stdio.h>
typedef struct
{
int x;
int y;
} point_t;
int binary_search(int *ary, unsigned int ceiling, int target)
{
unsigned int floor = 0;
while (ceiling > floor)
{
unsigned int pivot = (ceiling + floor) / 2;
if (ary[pivot] < target)
floor = pivot + 1;
else if (ary[pivot] > target)
ceiling = pivot;
else
return pivot;
}
return -1;
}
void set_point_data(point_t *point_in, point_t *p_point_in, int x, int y)
{
point_in->x = x;
point_in->y = y;
return;
}
int main(int argc, char *argv)
{
int a[] = {1, 2, 4, 5, 6};
point_t point1;
point_t *p_point;
printf("%d\r\n", binary_search(a, 5, 7)); /* -1 */
printf("%d\r\n", binary_search(a, 5, 6)); /* 4 */
printf("%d\r\n", binary_search(a, 5, 5)); /* 3 */
p_point = &point1;
point1.x = 78;
point1.y = 78;
set_point_data(&point1, p_point, 1, 2);
printf("%d %d\r\n", point1.x, point1.y);
printf("%d %d\r\n", p_point->x, p_point->y);
return 0;
}
.gdbinit 代码
layout src
# 导入 python 文件
source test.py
b main
b binary_search if target == 5
b set_point_data
# set_point_data 触发的命令
comm
# 向命令 print_point 传递的参数是 *point_in 和 p_point_in
print_point *point_in p_point_in
end
# 断点 1 触发执行的命令
command 1
i locals
i args
end
# 断点 2 触发执行的命令
comm 2
i locals
i args
end
python 代码
# 1. 导入gdb模块来访问gdb提供的python接口
import gdb
# 2. 用户自定义命令需要继承自gdb.Command类
class Move(gdb.Command):
# 3. docstring里面的文本是不是很眼熟?gdb会提取该类的__doc__属性作为对应命令的文档
"""Move breakpoint
Usage: mv old_breakpoint_num new_breapkpoint
Example:
(gdb) mv 1 binary_search -- move breakpoint 1 to `b binary_search`
"""
def __init__(self):
# 4. 在构造函数中注册该命令的名字
super(self.__class__, self).__init__("mv", gdb.COMMAND_USER)
# 5. 在invoke方法中实现该自定义命令具体的功能
# args表示该命令后面所衔接的参数,这里通过string_to_argv转换成数组
def invoke(self, args, from_tty):
argv = gdb.string_to_argv(args)
if len(argv) != 2:
raise gdb.GdbError('输入参数数目不对,help mv以获得用法')
# 6. 使用gdb.execute来执行具体的命令
gdb.execute('delete ' + argv[0])
gdb.execute('break ' + argv[1])
# 7. 向gdb会话注册该自定义命令
Move()
class print_point(gdb.Command):
def __init__(self):
super(self.__class__, self).__init__("print_point", gdb.COMMAND_USER)
def invoke(self, args, from_tty):
point = gdb.parse_and_eval('point_in')
print(point['x']) # 等价于 point.x
print(point['y']) # 等价于 point.y
point.referenced_value() # 等价于 &point
p = gdb.parse_and_eval('p_point_in')
point2 = p.dereference() # 等价于 *p
print(point2['x']) # 等价于 (*p).x,也即 p->x
print(point2['y']) # 等价于 (*p).y,也即 p->y
print_point()
效果
参考
https://github.com/kfggww/algorithms-in-c/tree/main
https://segmentfault.com/a/1190000005718889