一、背景说明
在进行 ClickHouse 的开发过程中,免不了需要应用 gdb 等调试工具来分析代码,进行调试。然而,在调试过程中,会发现很多变量在 print 时出现无法显示的状况。具体复现过程如下:
1.1 环境说明
| 环境 | 参数 |
| 操作系统 | CentOS Linux release 7.2.1511 |
| 内存大小 | 128GB |
| cmake | 3.17.3 |
| ninja | 1.10.0.git |
| clang | 10.0.0 |
| gdb | 9.2 |
| glibc | 2.31 |
| clickhouse | 21.3.1.1 (可以调试任意版本) |
1.2 首先,编译Debug版本的ClickHouse
$ git clone --recursive https://github.com/ClickHouse/ClickHouse.git$ cd ClickHouse$ mkdir build$ cd build$ cmake ../ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++-10 -DCMAKE_C_COMPILER=clang-10$ ninja
1.2 通过gdb调试
Server端:
$ gdb clickhouse$ (gdb) b arrayElement.cpp:472 //通过 b filename:行号 在相应位置设置断点$ (gdb) run server --config config.xml //启动server
Client端:
$ programs/clickhouse clientnode :) create table test (a Array(UInt32)) Engine = MergeTree() order by tuple();node :) insert into test values (array(1,2,3,4,5));node :) select a[1] from test;
1.3 出现问题:变量无法显示,函数定义找不到
(gdb) p col_array$1 = (const DB::ColumnArray *) 0x7fff0527e100(gdb) p *col_array$2 = <incomplete type>......(gdb) p ast$2 = {__ptr_ = 0x7fff1e0ab2d8, __cntrl_ = 0x7fff1e0ab2c0}(gdb) ptype(ast)type = class std::__1::shared_ptr<DB::IAST> [with _Tp = DB::IAST] {private:_Tp *__ptr_;std::__1::__shared_weak_count *__cntrl_;public:shared_ptr(void);......}(gdb) p DB::queryToString(ast)No symbol "queryToString" in namespace "DB".
二、原因分析
1.检查目标文件programs/clickhouse-server
是否有 debug_info
等debug相关的section,并且没有被strip。通过命令file programs/clickhouse-server
可以看类似with debug_info, not stripped
的信息。2.查看目标文件的.symtab, .strtab, .debug_info
等section里是否有DB::ColumnArray
和DB::queryToString
。通过命令readelf -S programs/clickhouse-server
来查看有哪些section,通过命令readelf -s/--debug-dump=debug_info programs/clickhousep-server
来查看section的具体内容。3.查看clang的编译选项,我们发现clang在默认情况下支持优化来减小debug size,而编译选项-fno-limit-debug-info
或-fstandalone-debug
则可以关闭这些优化,生成full debug info。这样就可以找到相应的Type信息能够打印变量值。4.默认情况下Clickhouse用Debug Build并使用lld链接器的话,就会加上链接选项-Wl,--gdb-index
来生成.gdb_index
section来加速gdb读取symbols(注:如果去掉该option,后期可以用gdb-add-index
命令来生成)。在默认编译命令下,生成的是一个不完备的.gdb_index,没有
DB::queryToString这些函数,从而打印的时候就找不到这个symbol。而lld默认情况下是得识别
.debug_pubname/types来调试,所以编译的时候需要加上
-ggnu-pubnames才能生成更全的
.gdb_index`.
所以,我们建议用如下的command来进行编译,这样就可以进行调试,不会出现变量/函数无法print的情况。
cmake ../src/ -DENABLE_TESTS=0 -DCLICKHOUSE_SPLIT_BINARY=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-ggnu-pubnames -fno-limit-debug-info" -DCMAKE_CXX_COMPILER=clang++-10 -DCMAKE_C_COMPILER=clang-10 -DCMAKE_EXE_LINKER_FLAGS="-Wl,--dynamic-linker,/lib64/ld-linux-x86-64.so.2"
三、定制Pretty-printer
ClickHouse内用了大量的多态,boost, libcxx,导致我们在显示内容时会看到一大堆信息,如打印IAST,会看到内容如下,内容一大堆。
(gdb) p ast$5 = {__ptr_ = 0x7fff31a64558, __cntrl_ = 0x7fff31a64540}(gdb) ptype(ast)type = class std::__1::shared_ptr<DB::IAST> [with _Tp = DB::IAST]......(gdb) p *ast$6 = (DB::IAST &) @0x7fff31a64558: {<std::__1::enable_shared_from_this<DB::IAST>> = {__weak_this_ = {__ptr_ = 0x7fff31a64558,__cntrl_ = 0x7fff31a64540}}, <DB::TypePromotion<DB::IAST>> = {<No data fields>}, _vptr$IAST = 0xbee8070 <vtable for DB::ASTSelectWithUnionQuery+16>,children = {<std::__1::__vector_base<std::__1::shared_ptr<DB::IAST>, std::__1::allocator<std::__1::shared_ptr<DB::IAST> > >> = {<std::__1::__vector_base_common<true>> = {<No data fields>}, __begin_ = 0x7fff32c18120, __end_ = 0x7fff32c18130, __end_cap_ = {<std::__1::__compressed_pair_elem<std::__1::shared_ptr<DB::IAST>*, 0, false>> = {__value_ = 0x7fff32c18130}, <std::__1::__compressed_pair_elem<std::__1::allocator<std::__1::shared_ptr<DB::IAST> >, 1, true>> = {<std::__1::allocator<std::__1::shared_ptr<DB::IAST> >> = {<No data fields>}, <No data fields>}, <No data fields>}}, <No data fields>}, static hilite_keyword = 0x8a749ef "\033[1m",static hilite_identifier = 0x94ad7dd "\033[0;36m", static hilite_function = 0x8bc802d "\033[0;33m", static hilite_operator = 0x8abb325 "\033[1;33m",static hilite_alias = 0x9427849 "\033[0;32m", static hilite_substitution = 0x8ce4129 "\033[1;36m", static hilite_none = 0x86502a2 "\033[0m"}
开发者在调试时,像AST类一般只关心具体是什么类型,sql内容是什么。
而GDB提供了一个很好的工具可以帮助我们来解决这个问题,就是用python插件来实现pretty-printer。其原理是通过实现对某个类型(如ColumnArray
,IAST
)对应的 to_string 函数,gdb 在 print 一个变量时会优先判断该类型是否有注册的 pretty-printer,有的话就调用注册进来的对应 to_string 函数。这样用户就可以定制化显示的内容。
在这里我们实现了一个支持libcxx, clickhouse的AST,DataType,Column,PODArray
在内的ClickHouse-pretty-printer。例如打印AST类型的时候,简洁明了的显示是什么类型以及sql语句是什么。
(gdb) p ast$3 = std::shared_ptr<DB::IAST> count 1, weak 1 containing = {__ptr_ = 0x7fff31a64558}(gdb) p *asttype=ASTSelectWithUnionQuery, sql="SELECT * FROM A"
该项目已放置在github上,欢迎下载使用 ClickHouse-pretty-printer[1]
四、总结
一般情况下,GDB调试不了就需要查看ELF文件是否有debug_xxx
等section,并判断这些section中是否有相应的symbol。再从编译选项上去看。以及如果有.gdb_index
来加速的话,判断这个section中是否有所需的symbol。
最后的最后,gdb的pretty printer可以很好的帮助我们更加简洁明了的显示所需的内容。赶快用起来吧~
References
[1]
ClickHouse-pretty-printer: https://github.com/hexiaoting/ClickHouse-pretty-printer




