从一个指针开始
除了进程的core dump, 还有一条有用的日志:
2022-10-10 12:25:02.233 7f57d70bf700 1 []-- [v2:192.168.56.102:6824/2753553827,v1:192.168.56.102:6825/2753553827] --> v1:192.168.56.103:6801/1057529 -- osd_op(unknown.0.17:3466 1.22 1:44de85d6:::client_192.168.56.101:head [omap-get-vals-by-keys] snapc 0=[] ondisk+read+known_if_redirected+full_force e132) v8-- 0x7f57f457d600 con 0x7f57f44f2000
从这条日志得知问题发生的时候,ceph-mds 是用的con工具(0x7f57f44f2000) 向OSD进行IO要求的。而这个指针地址便是我们打开宝藏的钥匙。
C++程序主流还是用的面向工具编程范式,常日都是利用一个类来聚合干系的变量。然后工具实例之间相互存在引用。上面提到的指针0x7f57f44f2000 便是AsyncConnection的实列。不变式:虽然不同的进程,工具地址不同,但是工具内变量之间的偏移是确定。比如我想知道当前ceph-mds跟哪些节点建立了网络连接,我只要知道async_msgr的指针地址,就可以知道所有的网络连接。而async_msgr地址是AsyncConnection实例的成员变量。

以下是gdb的python插件脚本
class AsyncConnection(): def __init__(self, addr) : self.addr = addr self.msgr = read_ptr(addr+96) # 偏移量96是之前剖析有符号表的时候,记录的。class AsyncMessenger(): def __init__(self, addr): self.addr = addr conns_addrs = UnorderedMap(addr+1792, allocator_sz=0) # 1792 同上
因此通过AsyncConnection(0x7f57f44f2000) ,得到AsyncMessenger地址为0x7f57f365a000,进一步地就可以知道所有的连接信息
更大的困难我真正想得到的是mds(class MDSDaemon)工具的地址,由于只要拿到它的信息,基本上所有的数据构造信息都能看了。
class MDSDaemon : public Dispatcher {AsyncMessenger messenger; // messenger偏移量是768}
现在我知道了AsyncMessenger的地址0x7f57f365a000,怎么反向推导出MDSDaemon实列的地址addr, 知足下面的等式:
read_ptr (addr + 768) = 0x7f57f365a000注: read_ptr方法的功能:从一个内存地址读取8个字节。
而能解此方程方法便是遍历内存。
遍历内存ceph-mds进程利用的是tcmalloc库来管理内存分布,我们要做的实在便是去剖析tcmalloc的数据构造,而tcmalloc是开源组件,它的debug symbol很随意马虎获取。tcmalloc剖析不展开,网上很多资源。下面代码的基本逻辑便是
gdb 命令info variables pageheap_拿到pageheap_地址在pageheap_里遍历已经分配的span在span里面搜索知足一定大小的内存块在知足条件的内存块偏移量768的地方搜索0x7f57f365a000def walk_heap(page_heap, call_back): # the first level for index, leaf_ptr in page_heap.pagemap_.root.items(): print("{0} -> {1}".format(index, my_hex(leaf_ptr))) leaf = Leaf(leaf_ptr) for indx, span_ptr in leaf.values.items(): span = SpanItem(span_ptr) # only check INUSE span if span.location != 0: continue # cut span to chunks span.parse() call_back(span)def get_chunk_func(blk_sz, offset, value): def callback_func(span): if span.blk_sz < blk_sz: return for chunk in span.chunks: v = read_ptr(chunk.addr + offset) if v == value: print("Congratuation, you got it!") span.display() chunk.display() return callback_funccall_back_func = get_chunk_func(2144, 768, 0x7f57f365a000)walk_heap(heap, call_back_func)
运行结果
(gdb) source theap.py 65199 -> 0x7f57f22c3000Congratuation, you got it!Span(0x7f57f22442c0): objects:0x7f57f365c400, location:INUSE, next:0x7f57e8071c20, chunk count:7, blk size:23040x7f57f365a900~2304
0x7f57f365a900 便是我们要找的MDSDaemon地址,至此我们得到了打开宝藏大门的钥匙。小结
在ToB领域,碰着的困难常日是在有限的信息下如何快速地定位问题;而core dump信息无疑是最大的信息来源。通过利用gdb python API可以让剖析core dump变得更系统,更有效;剖析的过程通过脚本化也随意马虎沉淀下来。