配置

set print pretty on
set print elements unlimited
set pagination off

python 脚本

直接安装的 gdb 都是预先编译 python 的,可以使用 python 实现一些有用的功能,比如自定义命令,打印自定义类型等。可以参考 gdb python 扩展

gdb python 交互

(gdb) python
>print(1+1)
>end
2
(gdb) python
>v = 3
>end
(gdb) python print(v)
3
(gdb)
  • python 解释器中的值不是临时变量,可以定义之后后续使用

重点 API

  1. gdb.Value ,直接在 gdb 中访问的值,在 python 中,被包装为 gdb.Value 对象,包含对象的具体值,类型等信息。

    # struct box
    # {
    #     int a;
    #     int b;
    # };
    
    # using box_ptr = box *;
    # using box_ref = box &;
    
    # box *b1 = new box{1, 2};
    # box b2 = box{3, 4};
    # box_ptr b3 = b1;
    # box_ref b4 = b2;
    
    b1 = gdb.parse_and_eval('b1')
    b2 = gdb.parse_and_eval('b2')
    b3 = gdb.parse_and_eval('b3')
    b4 = gdb.parse_and_eval('b4')
    
    print('address b1 {}, b2 {}, b3 {}, b4 {}'.format(b1.address, b2.address, b3.address, b4.address))
    address b1 0x7fffffffda58, b2 0x7fffffffda88, b3 0x7fffffffda60, b4 0x7fffffffda88
    print('type b1 {}, b2 {}, b3 {}, b4 {}'.format(b1.type, b2.type, b3.type, b4.type))
    type b1 box *, b2 box, b3 box_ptr, b4 box_ref
    
    (gdb) python print(b1.dereference())  
    {a = 1, b = 2}
    (gdb) python print(b2.dereference())  
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
    gdb.error: Attempt to take contents of a non-pointer value.
    Error while executing Python code.
    (gdb) python print(b3.dereference())  
    {a = 1, b = 2}
    (gdb) python print(b4.dereference())  
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
    gdb.error: Attempt to take contents of a non-pointer value.
    Error while executing Python code.
    
    (gdb) python print(b1.referenced_value())
    {a = 1, b = 2}
    (gdb) python print(b2.referenced_value())
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
    gdb.error: Trying to get the referenced value from a value which is neither a pointer nor a reference.
    Error while executing Python code.
    (gdb) python print(b3.referenced_value())
    {a = 1, b = 2}
    (gdb) python print(b4.referenced_value())
    {a = 3, b = 4}
    
    (gdb) python print(b1.reference_value())
    @0x7fffffffda58
    (gdb) python print(b2.reference_value())
    @0x7fffffffda88
    (gdb) python print(b3.reference_value())
    @0x7fffffffda60
    (gdb) python print(b4.reference_value())
    @0x7fffffffda88
    
    (gdb) python print(b1.const_value())
    0x55555556d750
    (gdb) python print(b2.const_value())
    {a = 3, b = 4}
    (gdb) python print(b3.const_value())
    0x55555556d750
    (gdb) python print(b4.const_value())
    @0x7fffffffda88
    
  2. gdb.Type ,类型,包含类型信息,比如指针,数组,结构体等。可以使用 lookup_type 转换类型为 内置 gdb.Type 对象。然后使用 gdb.Value.cast 方法转换为指定类型。

    (gdb) p * best_path
    $3 = {
      type = T_ProjectionPath,
      pathtype = T_Result,
      parent = 0x563e104048b0,
      pathtarget = 0x563e10404780,
      param_info = 0x0,
      parallel_aware = false,
      parallel_safe = false,
      parallel_workers = 0,
      rows = 1,
      startup_cost = 0.047500000000000014,
      total_cost = 0.052500000000000012,
      pathkeys = 0x563e103fb180
    }
    (gdb) python path = gdb.parse_and_eval('best_path')
    (gdb) python print(path.type)
    Path *
    (gdb) python print(gdb.lookup_type('ProjectionPath').pointer())
    ProjectionPath *
    (gdb) python print(gdb.lookup_type('ProjectionPath').reference())
    ProjectionPath &
    (gdb) python print(path.cast(gdb.lookup_type('ProjectionPath').pointer()).type)
    ProjectionPath *
    (gdb) python print(path.cast(gdb.lookup_type('ProjectionPath').pointer()).referenced_value())
    {
      path = {
        type = T_ProjectionPath,
        pathtype = T_Result,
        parent = 0x563e104048b0,
        pathtarget = 0x563e10404780,
        param_info = 0x0,
        parallel_aware = false,
        parallel_safe = false,
        parallel_workers = 0,
        rows = 1,
        startup_cost = 0.047500000000000014,
        total_cost = 0.052500000000000012,
        pathkeys = 0x563e103fb180
      },
      subpath = 0x563e10404668,
      dummypp = true
    }
    
  3. gdb.parse_and_eval 方法,可以解析表达式,获取表达式的值。例如具体的某个变量,或者函数等。

自定义命令

继承 gdb.Command 类,实现 invoke 方法,下面是一个小 demo

(gdb) python
>class HelloWorld (gdb.Command):

  def __init__ (self):
    super (HelloWorld, self).__init__ ("pp", gdb.COMMAND_USER)

  def invoke (self, args, from_tty):
    argv = gdb.string_to_argv (args)
    if len (argv) != 1:
      raise gdb.GdbError ("pp takes no arguments")
    print (argv)

HelloWorld ()
>end
(gdb) pp
pp takes no arguments
(gdb) pp 3
['3']
(gdb)

pretty printer

对于复杂的大型程序, 特别是 C 程序,由于不直接支持多态,所以会使用一些取巧的方式,例如 Postgres 中,大部分结构体都直接或者间接的继承自 Node ,大部分指针 直接就是一个 Node*, 不一层一层展开,是不知道 Node* 的指针的 8 字节下到底隐藏着什么深渊巨兽。有时候可能为了查看某个执行计划中的某个字段具体是什么,可能需要反复的 进行强转打印

(gdb) p * ((JoinPath*)((JoinPath*)((SortPath*)(((ProjectionPath*)best_path)->subpath))->subpath)->outerjoinpath)->outerjoinpath
$25 = {
  type = T_Path,
  pathtype = T_SeqScan,
  parent = 0x563e103ded10,
  pathtarget = 0x563e103def18,
  param_info = 0x0,
  parallel_aware = false,
  parallel_safe = true,
  parallel_workers = 0,
  rows = 1,
  startup_cost = 0,
  total_cost = 0,
  pathkeys = 0x0
}

当然可以使用 pprint ,但是 pprint 只能打印继承自 Node 的对象,对于其他类型,pprint 则无能为力,而且 pprint 最大的缺点是无法在 core 文件中使用,此时会提示 You can't do that without a process to debug.

此外部分人应该使用过 https://github.com/tvondra/gdbpg.git 这个程序,可能也针对特定的 postgres 版本做了一些定制。 我当前所在公司是某个 pg 的衍生数据库, 我也想使用这个脚本做下扩展,方便自己使用,但是某天突然想调试下其他 pg 类数据库,例如 gp, tbase 啥的,此时也需要再取进行扩展,但是由于不是太熟悉,一时之间不知道什么该打印,什么不该打印,中间容易遇到问题,弄来弄去很麻烦,所以就放弃了。

好在 gdb 提供 pretty printer 机制,可以自定义打印方法,C++ 的 STL 库就提供了对应的 python 脚本,具体文件在 /usr/share/gcc/python/libstdcxx/v6 目录下,感兴趣的可以看看怎么是实现的。

下面是在实现 postgres pretty printer 的功能中的一些问题和思考。

首先是官方的一个小 demo , 做了点小修改,方便理解下面的讲解

'''
struct foo { int a, b; };
struct bar { struct foo x, y; };
'''

import gdb

class fooPrinter:
    """Print a foo object."""

    def __init__(self, val):
        self.__val = val

    def to_string(self):
        return ("a=<" + str(self.__val["a"]) +
                "> b=<" + str(self.__val["b"]) + ">")

class barPrinter:
    """Print a bar object."""

    def __init__(self, val):
        self.__val = val

    def to_string(self):
        return ("x=<" + str(self.__val["x"]) +
                "> y=<" + str(self.__val["y"]) + ">")

def build_pretty_printer():
    pp = gdb.printing.RegexpCollectionPrettyPrinter(
        "my_library")
    pp.add_printer('foo', '^foo$', fooPrinter)
    pp.add_printer('bar', '^bar$', barPrinter)
    return pp

gdb.printing.register_pretty_printer(
    gdb.current_objfile(),
    build_pretty_printer())

1. gdb pretty printer 实现原理

具体参考 gdb 源码中的 py-prettyprint.cprinting.py 文件,大意是:

  1. gdb.pretty_printers 中注册一个 printer,在上面的例子中,这步由 register_pretty_printer 实现, register_pretty_printer 会把 RegexpCollectionPrettyPrinter 对象添加到 gdb.pretty_printers 中,可以查看 对应的 gdb 中的源码,其实现核心是 obj.pretty_printers.insert(0, printer)

  2. 注册的这个 printer 必须实现 __call__,具体可以参考 gdb 内置的 RegexpCollectionPrettyPrinter 或者 STL 库中的 Printer 等。其内部主要是一个数组,包含具体的对应某类型的 printer, gdb 在调用的时候,在 __call__中,获取 Value 的 type, 然后由这个 type 找到数组中具体的 printer 类,然后构造一个对象返回

        def __call__(self, val):
            """Lookup the pretty-printer for the provided value."""
    
            # Get the type name.
            typename = gdb.types.get_basic_type(val.type).tag
            if not typename:
                typename = val.type.name
            if not typename:
                return None
    
            # Iterate over table of type regexps to determine
            # if a printer is registered for that type.
            # Return an instantiation of the printer if found.
            for printer in self.subprinters:
                if printer.enabled and printer.compiled_re.search(typename):
                    return printer.gen_printer(val)
    
            # Cannot find a pretty printer.  Return None.
            return None
    
  3. 对于具体类型的 printer,需要实现 to_string 方法,可选的为 childrendisplay_hint 方法等,具体说明参考官方文档,这是我们实现自定义 pretty printer 的关键步骤。在 gdb 中,获取 printer 对象之后, 其内部会使用 C 代码调用对象的实现的函数,实现打印功能,具体函数为 pretty_print_one_value

2. 实现自定义 pretty printer

我们可以仿照 demo 实现自定义 printer,但是在 pg REL_16_STABLE 中, nodetags.h 中定义了 454 个类型,如果一一实现是不现实的,此外还得兼顾不同的版本,不同的 pg 类数据库等。想想在 gdb 调试中, 突然发现某个类型无法打印,甚至出错了导致 gdb 崩掉的情况,好好的思路就这么呗打断了,真的无法忍受,为了可以快速实现 printer 添加修改,这里实现的其实不是 pretty printer, 而应该是 pretty printer generator

  1. 快速添加一个 printer

    如 demo 中, 实现具体的 printer 之后, 使用 add_printer 添加到 RegexpCollectionPrettyPrinter 中, 如果有几百上千个类型, 手动添加会很麻烦, 所以可以写一个装饰器, 自动注册 printer,具体代码如下:

    
    printer = gdb.printing.RegexpCollectionPrettyPrinter('REL_16_STABLE')
    
    def register_printer(name):
        def __registe(_printer):
            printer.add_printer(name, '^' + name + '$', _printer)
        return __registe
    
    @register_printer('Relids')
    class RelidsPrinter:
        def __init__(self, val) -> None:
            self.val = val
    
        def to_string(self):
            list = []
            rid = gdb.parse_and_eval('bms_next_member(({}) {}, -1)'.format(self.val.type.pointer(), self.val.dereference().address))
            while rid >= 0:
                list.append(int(rid))
                rid = gdb.parse_and_eval('bms_next_member(({}) {}, {})'.format(self.val.type.pointer(), self.val.dereference().address, rid))
    
            return str(list)
    
    gdb.printing.register_pretty_printer(
        gdb.current_objfile(),
        printer, True)
    
  2. 实现 printer generator,怎么动态生成,怎么获取想要打印对象的结构

    如果不要求个性化,则大部分的 printer 是类似的,例如 FuncExpr ,手写的话大概类似这个

    @register_printer('FuncExpr')
    class FuncExprPrinter:
        def __init__(self, val) -> None:
            self.val = val
    
        def to_string(self):
            return 'FuncExpr[funcid: %s, funcresulttype: %s, funcretset: %s, funcvariadic: %s, funcformat: %s, funccollid: %s, inputcollid: %s]' % (
                self.val['funcid'],
                self.val['funcresulttype'],
                self.val['funcretset'],
                self.val['funcvariadic'],
                self.val['funcformat'],
                self.val['funccollid'],
                self.val['inputcollid']
            )
    
        def children(self):
            list = []
            add_list(list, self.val, 'args')
            return list
    

    pg Node 结构体要求其子类实现一些特定函数, 以便于 pg 实现特定的功能,例如 read, write等,之前都是 write by hand ,后面实现了一个脚本 gen_node_support.pl,用来处理这个重复的过程,我们只需要稍微改动下这个文件,把结构体导出即可,然后再稍微处理下, 就获得了我们想要的格式,当前所有的可打印的结构体都在 node_struct.py

    RangeVar = [
      ('char*', 'catalogname'),
      ('char*', 'schemaname'),
      ('char*', 'relname'),
      ('bool', 'inh'),
      ('char', 'relpersistence'),
      ('Alias*', 'alias'),
      ('int', 'location'),
    ]
    

    如果仿照 FuncExpr 实现一个 RangeVarPrinter,结构是类似的,所以可以实现一个公有方法,解析这个结构体,把指针和其他类型区分开,指针放在 children 中打印,其他的放在 to_string 中打印即可,可以参考 postgres pretty printer 中的 split_field 函数和 print_to_stringprint_children,大部分逻辑在 BasePrinter 类下面

  3. 怎么处理继承关系

    对于继承关系,为了方便处理,对应子类的结构体中需要加上父类结构体的字段,在 python 中获取值得时候,解析字段为数组,然后使用类似 val['path']['pathtype'] 的语法来获取,但是继承的深度不确定,例如 ('NodeTag', 'jpath.path.pathtype'),,所以需要动态获取,这里使用 python 的 reduce 函数 reduce(operator.getitem, fields, self.val)

    IndexPath = [
      ('NodeTag', 'path.pathtype'),
      ('RelOptInfo*', 'path.parent'),
      ...
      ('ScanDirection', 'indexscandir'),
      ('Cost', 'indextotalcost'),
      ('Selectivity', 'indexselectivity'),
    ]
    
  4. 怎么打印 数组

    对于数组,典型的就是 Sort 中的 key 的操作符信息,这里需要在结构体中标明长度信息, 然后解析长度,再使用长度获取对应字段信息,具体参考 print_children_array 函数

  5. 特殊函数

    一些特殊结构体,无法使用上面得方法实现,所以还是需要自己手写,例如 List ,Bitmapset 以及 Value 等

3. 在上面的基础上快速添加或修改结构体

gen_node_support.pl 是比较新的 pg 版本上才会有的,所以其他版本无法直接使用这个文件处理结构体,所以需要自己手动查找修改,虽然各个版本之间差异点不大,但是从几百个结构体里面找不同还是挺难的,为了方便快速的添加或修改,这里提供一个 trace 文件,类似 pstack,按照自己的具体的文件添加 断点,attach 上进程之后,打印关键 结构体,即可快速查找出差异点, 然后快速修改,当前是使用 tpch 的语句 测试的,暂时没有使用其他复杂语句或类型测试,后续发现了再慢慢添加修改,这里应该可以覆盖大部分常用场景了

  • trace 文件还可作为日常的调试工具,如果只是想简单调试,无需手工接入的话,可以直接 attach 上,直接添加自己想要trace 的信息即可

attention

  • 当前支持的是四个等级,使用 print pg_pretty 控制

    • off 为关闭打印
    • origin 会使用 pprint 打印,需要注意的是由于无法确定 print 的时候,指针具体是什么类型, 也不想为每个类型写个正则, 所以只能把指针转换为 Node 之后再打印,例如p *(Node*) best_path, 需要注意的是 pprint 是 builtin function,所以如果是调试 core 文件,是无法使用此等级的
    • trace 会使用 python 脚本中实现的 printer ,预先设计是可以在任何地方使用,但是当前 Bitmapset 使用了 pg 内置的 bmsToString 函数,暂时无法直接在调试 core 的时候使用,这里暂时没有这个需求,所以就先暂时搁置,以后需要的时候再实现。这里其实也简单尝试过, 实现的 bms_next_member 函数单独使用的时候还是可以正常工作的,但是循环使用的时候会死循环, 不知道为啥。。知道是 bug 还是实现有问题,后面再看
    • info 简化打印信息,实现类似 explain 的输出的样式,但是这个需要手写,暂时没有实现。预估难点和功能为
      1. 格式是什么
      2. printer 之间无法传递上下文信息,类似 Var 这种结构体不知道归属,可能需要自己改造 RegexpCollectionPrettyPrinter
      3. 单独打印某个结构体的时候,能否能从某个地方获取某些信息,例如 PlannerInfo 中的 simple_rel_array
  • 实现中注意相互引用问题, 典型的例如 RestrictInfo,一些结构体中的字段可能保存的上层的某个结构体,此时不能打印

TODO

  1. 完善功能,支持更多结构体,支持更多的类型
  2. 抽取公共部分,提高复用性,实现一个 gdb pretty printer generator,可以自动生成 printer, 并且可以指定结构体的版本,自动处理继承关系,自动处理数组等,以支持除 pg 之外的其他项目
  3. 是否需要实现一个 pg 插件,配合使用,python 无法实现的功能在插件中实现,然后使用 python 来调用
  4. 完善测试,目前只是简单测试了几个结构体,后续需要测试更多的结构体,包括复杂的结构体,例如复杂的查询计划,复杂的函数调用,复杂的表达式等等
  5. 完善文档,包括 README, 如何使用,如何开发,如何测试等等
  6. 使用系统函数获取某些元数据信息,是否会影响程序的运行,原来的缓存是否会被污染,是否需要支持高的等级
  • 项目还在演化中,某些功能可能会变

auto load

gdb 的 auto-load 机制,可以实现在 gdb 启动的时候自动加载指定的脚本,这样就不需要手动 source 了。且可以限制加载的范围,只加载指定的程序。如果没有限定 pretty printer 的使用范围 , 直接在 .gdbinit 中 source python 脚本,则脚本内容是全局的,此时任何的程序只要有相同名字的结构体,都会触发 prtty printer。但是具体的结构体内容不一样,直接使用是有问题的,所以对于自己的 pretty printer ,还是有必要限定使用范围。

(gdb) info pretty-printer
global pretty-printers:
  builtin
    mpx_bound128
  REL_16_STABLE
    (Node|Expr)
    A_ArrayExpr
    A_Const
    A_Expr
    A_Indices
    A_Indirection
    A_Star
    AccessPriv
    Agg
    AggPath
    Aggref
    Alias

详情参考官方文档,这里不多赘述,重点总结下

  1. auto-load 脚本的名称,需要匹配你想动态加载的文件的名称,例如的 libstdc++.so.6.0.31 文件,匹配的文件名称为 libstdc++.so.6.0.31-gdb.py。当然由于 gdb 的脚本文件不仅仅只支持 python, 所以 auto-load 也进行文件名的区分,具体加载什么取决于你提供的文件,下面是一个 gdb 加载 libstdc++.so.6.0.32-gdb.py 的 trace

    readlink("/usr", 0x7ffd8c8eb4d0, 1023)  = -1 EINVAL (Invalid argument)
    readlink("/usr/lib", 0x7ffd8c8eb4d0, 1023) = -1 EINVAL (Invalid argument)
    readlink("/usr/lib/x86_64-linux-gnu", 0x7ffd8c8eb4d0, 1023) = -1 EINVAL (Invalid argument)
    readlink("/usr/lib/x86_64-linux-gnu/libstdc++.so.6", "libstdc++.so.6.0.32", 1023) = 19
    readlink("/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.32", 0x7ffd8c8eb4d0, 1023) = -1 EINVAL (Invalid argument)
    openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.32-gdb.gdb", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
    openat(AT_FDCWD, "/usr/lib/debug/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.32-gdb.gdb", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
    openat(AT_FDCWD, "/usr/share/gdb/auto-load/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.32-gdb.gdb", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
    readlink("/lib", "usr/lib", 1023)       = 7
    readlink("/usr", 0x7ffd8c8eb4d0, 1023)  = -1 EINVAL (Invalid argument)
    readlink("/usr/lib", 0x7ffd8c8eb4d0, 1023) = -1 EINVAL (Invalid argument)
    readlink("/usr/lib/x86_64-linux-gnu", 0x7ffd8c8eb4d0, 1023) = -1 EINVAL (Invalid argument)
    readlink("/usr/lib/x86_64-linux-gnu/libstdc++.so.6", "libstdc++.so.6.0.32", 1023) = 19
    readlink("/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.32", 0x7ffd8c8eb4d0, 1023) = -1 EINVAL (Invalid argument)
    openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.32-gdb.py", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
    openat(AT_FDCWD, "/usr/lib/debug/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.32-gdb.py", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
    openat(AT_FDCWD, "/usr/share/gdb/auto-load/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.32-gdb.py", O_RDONLY|O_CLOEXEC) = 16
    
  2. auto-load 脚本的路径一般在 /usr/share/gdb/auto-load//usr/lib/debug 目录下,在此目录下建立一个你需要动态加载的文件的全目录类似的目录结构,例如 usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.31-gdb.py ,此时你的文件的全路径就是 /usr/share/gdb/auto-load/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.32-gdb.py ,一般系统下还有其他文件,可以自行测试

    ❯ tree
    .
    ├── lib
    │   └── x86_64-linux-gnu
    │       └── libpthread-2.35.so-gdb.py
    └── usr
        ├── lib
        │   └── x86_64-linux-gnu
        │       ├── libisl.so.23.1.0-gdb.py
        │       └── libstdc++.so.6.0.32-gdb.py
        └── lib32
            └── libstdc++.so.6.0.32-gdb.py
    
    6 directories, 4 files
    /usr/share/gdb/auto-load ❯
    
  3. 此外,脚本文件也可以放在你调试文件的目录下,此时只要加载到此文件,则也会同时加载此文件,但是需要注意的是需要在 .gdbinit 中添加 add-auto-load-safe-path {your_path} , 否则 gdb 会提示 auto-loading has been declined by your `auto-load safe-path' set to "$debugdir:$datadir/auto-load".,加载之后,查看 auto-load safe-path 如下

    (gdb) show auto-load safe-path
    List of directories from which it is safe to auto-load files is $debugdir:$datadir/auto-load:/home/asky/.build/bin.
    
  4. 此时 gdb 启动之后,再查看 pretty printer,会看见 pretty printer 不在 global pretty-printers 下面了,而是和文件关联在一起

    global pretty-printers:
      builtin
        mpx_bound128
    objfile /home/asky/.build/bin/postgres pretty-printers:
      REL_16_STABLE
        (Node|Expr)
        A_ArrayExpr
        A_Const
        A_Expr
        A_Indices
        A_Indirection
        A_Star
        AccessPriv
        Agg
        AggInfo
        AggPath
    

不仅仅只是 pretty printer ,其他的脚本文件也可以使用类似的方法和程序绑定在一起,避免了不同程序之间相互干扰的问题

checkpoint

  • parse_and_eval 可以运算表达式,然后保存为 python 内置变量,运用范围极广

  • vscode 也可以使用 pretty printer,只需要按照上面的步骤实现自定义的 printer, 然后设置 auto load 即可

    1. vscode 窗口中显示变量值需要要求是对象,而不能是指针
    2. 窗口中显示的值需要在 children 中返回才显示, to_string 显示的值窗口是不会显示的,例如 vector gdb 中显示的是 $1 = std::vector of length 4, capacity 4 = {"hello", "world", "c++", "python"},而窗口中只显示实际的内容 {"hello", "world", "c++", "python"},to_string 更类似是一个 prompt
    3. 所以自定义的 printer 实现的时候,需要注意什么是重点, 什么是可以忽略的,打印主体需要放在 children 中
  • 可以使用 python 直接从程序中解析结构体,然后构造 printer ,理论可行,但是这个过程完全不可控,且难以实现

(gdb) b planner
Breakpoint 1 at 0x55a0564fc233: file /workspaces/postgres/src/backend/optimizer/plan/planner.c, line 397.
(gdb) c
Continuing.

Breakpoint 1, planner (parse=0x55a05825e918, ...) at /workspaces/postgres/src/backend/optimizer/plan/planner.c:397
397             if (planner_hook)
(gdb) python v = gdb.parse_and_eval('parse')
(gdb) python print(gdb.types.get_basic_type(v.dereference().type).fields())
[<gdb.Field object at 0x7fea1ad730b0>, <gdb.Field object at 0x7fea1ad73730>, <gdb.Field object at 0x7fea1ad71970>, <gdb.Field object at 0x7fea1ad731d0>, <gdb.Field object at 0x7fea1ad72bf0>, <gdb.Field object at 0x7fea1ad71f90>, <gdb.Field object at 0x7fea1ad71ed0>, <gdb.Field object at 0x7fea1ad734d0>, <gdb.Field object at 0x7fea1ad73950>, <gdb.Field object at 0x7fea1ad70cd0>, <gdb.Field object at 0x7fea1ad72e70>, <gdb.Field object at 0x7fea1ad72ff0>, <gdb.Field object at 0x7fea1ad73070>, <gdb.Field object at 0x7fea1ad73db0>, <gdb.Field object at 0x7fea1ad72370>, <gdb.Field object at 0x7fea1ad71f70>, <gdb.Field object at 0x7fea1ad72b30>, <gdb.Field object at 0x7fea1ad73eb0>, <gdb.Field object at 0x7fea1ad71630>, <gdb.Field object at 0x7fea1ad70e50>, <gdb.Field object at 0x7fea1ad70cb0>, <gdb.Field object at 0x7fea1ad70db0>, <gdb.Field object at 0x7fea1ad73590>, <gdb.Field object at 0x7fea1ad70950>, <gdb.Field object at 0x7fea1ad726f0>, <gdb.Field object at 0x7fea1ad72950>, <gdb.Field object at 0x7fea1ad73470>, <gdb.Field object at 0x7fea1ad72e30>, <gdb.Field object at 0x7fea1ad71b10>, <gdb.Field object at 0x7fea1ad71c70>, <gdb.Field object at 0x7fea1ad73150>, <gdb.Field object at 0x7fea1ad72830>, <gdb.Field object at 0x7fea1ad71710>, <gdb.Field object at 0x7fea1ad72890>, <gdb.Field object at 0x7fea1ad72850>, <gdb.Field object at 0x7fea1ad719f0>, <gdb.Field object at 0x7fea1ad72530>, <gdb.Field object at 0x7fea1ad736b0>, <gdb.Field object at 0x7fea1ad71590>, <gdb.Field object at 0x7fea1ad72670>, <gdb.Field object at 0x7fea1ad713b0>, <gdb.Field object at 0x7fea1ad739f0>, <gdb.Field object at 0x7fea1ad71750>, <gdb.Field object at 0x7fea1ad73a10>]
(gdb) python print(gdb.types.get_basic_type(v.dereference().type).fields()[0].type)
NodeTag
(gdb) python print(gdb.types.get_basic_type(v.dereference().type).fields()[1].type)
CmdType
(gdb)
  • 可以把复杂逻辑交给程序实现,然后提供函数接口给 python, python 直接调用函数打印对象,例如可以实现一个 pg 的插件,print 的在插件中实现。

i am stupid

当前的实现存在巨大问题,所有的pinter 聚在一起, 出了问题根本无法定位是那个printer的锅,把环境从 Ubuntu 22 升级到 Ubuntu 24 之后, 整个就几乎 gg 了 后续考虑显式实现每一个类型的printer,当然不能手写,打算写个插件实现。printer gennerator 就不应该交给 python 这种语言,弱类型,太自由了