暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

GDB调试ClickHouse的那些坑

ClickHouse研发笔记 2021-03-05
3174

一、背景说明


在进行 ClickHouse 的开发过程中,免不了需要应用 gdb 等调试工具来分析代码,进行调试。然而,在调试过程中,会发现很多变量在 print 时出现无法显示的状况。具体复现过程如下:

1.1 环境说明

环境参数
操作系统CentOS Linux release 7.2.1511
内存大小128GB
cmake3.17.3
ninja1.10.0.git
clang10.0.0
gdb9.2
glibc2.31
clickhouse21.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 client
node :) 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 *ast
type=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


文章转载自ClickHouse研发笔记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论