背景

近日有小伙伴遇到了一个生产问题,pg_rewind命令会偶发的出现这个错误消息

  1. ... // rewind 过程信息
  2. The program "initdb" was found by "xxx/bin/pg_rewind"
  3. but was not the same version as pg_rewind.
  4. Check your installation.
  5. Failure,exiting

各种环境检查、验证搞了一通之后没什么头绪,找我帮忙,我看了看代码,感觉是个bug,那就只能debug试试了。

网上逛了一圈发现,大部分教人跟踪PG源码的帖子,都是gdb attach进程的方式,并不适合这个场景,pg_rewind是个命令,一气呵成,中途不会有机会让你停下来去attach一把的。

研究了下gdb的help信息,发现--args选项可行。
问题调查完,确实是个bug,但并不是社区版PG的bug,是我们定制版PG的bug。原因不重要,但这个调查方法我觉得可以总结一下。

跟踪PG进程的两条路

场景一、跟踪SQL进程

SQL的执行是在建立连接之后,因此,可以在建立连接之后,执行SQL之前,通过gdb的方式attach进程,附加断点,然后debug跟踪,举个栗子,开2个窗口,一边执行SQL,一边debug

  • 窗口一:建连接,取pid
  1. [guqi@localhost ~]$ psql -p 51005
  2. psql (xxxx based on PG 11.6)
  3. Type "help" for help.
  4. postgres=# select pg_backend_pid();
  5. pg_backend_pid
  6. ----------------
  7. 52069
  8. (1 row)
  • 窗口二:gdb attch pid
  1. -- 格式:gdb postgres命令的路径 pid
  2. [root@localhost ~]# gdb /data/postgres/app/bin/postgres 52069
  3. GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-119.el7
  4. Copyright (C) 2013 Free Software Foundation, Inc.
  5. License GPLv3+: GNU GPL version 3 or later
  6. ...
  7. 0x00007fe6060bef23 in __epoll_wait_nocancel () from /lib64/libc.so.6
  8. Missing separate debuginfos, use: debuginfo-install glibc-2.17-317.el7.x86_64
  9. (gdb)

场景二、跟踪PG命令

PG安装路径的bin目录内有很多封装好的二进制命令,这些不像SQL需要单独建连接执行,因此跟踪这些命令的执行,也不能像场景一那样可以事先打好断点。

strace跟踪

strace可以跟踪命令执行过程中的系统调用,并且-tt选项可以打印调用的时间点,举个栗子:

  1. [guqi@localhost ~]$ strace -tt createdb -h 127.1 -p 51005
  2. 14:59:41.171926 execve("/data/guqi/postgres/app/bin/createdb", ["createdb", "-h", "127.1", "-p", "51005"], 0x7ffc4ad5d878 /* 31 vars */) = 0
  3. 14:59:41.172721 brk(NULL) = 0xa55000
  4. 14:59:41.172861 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc1f4f1f000
  5. 14:59:41.172982 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
  6. 14:59:41.173238 open("/data/guqi/postgres/app/lib/tls/x86_64/libpq.so.5", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
  7. ...
  8. 14:59:41.207330 sendto(3, "X\0\0\0\4", 5, MSG_NOSIGNAL, NULL, 0) = 5
  9. 14:59:41.207446 close(3) = 0
  10. 14:59:41.207641 exit_group(1) = ?
  11. 14:59:41.208050 +++ exited with 1 +++
  12. [guqi@localhost ~]$

gdb跟踪

gdb可以直接执行一个二进制命令

  1. gdb [options] [executable-file [core-file or process-id]]

但是默认情况下,这个executable-file不能带参数,否则会报错。gdb提供了一个--args选项,可以传递参数。进入gdb交互之后,start开始运行命令,gdb会在主函数的入口处自动打个断点(挺人性的),之后就和场景一一样了。

  1. [guqi@localhost ~]$ gdb --args /data/guqi/postgres/bin/pg_rewind -D /data/guqi/data/master --source-server="host=127.0.0.1 port=51101 user=guqi"
  2. GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-119.el7
  3. Copyright (C) 2013 Free Software Foundation, Inc.
  4. License GPLv3+: GNU GPL version 3 or later
  5. ...
  6. Reading symbols from /data/guqi/postgres/app/bin/pg_rewind...done.
  7. (gdb) b 433
  8. Breakpoint 1 at 0x40294e: file /data/guqi/src/build_alone/../xxx/src/bin/pg_rewind/pg_rewind.c, line 433.
  9. (gdb) info b
  10. Num Type Disp Enb Address What
  11. 1 breakpoint keep y 0x000000000040294e in main
  12. at /data/guqi/src/build_alone/../xxx/src/bin/pg_rewind/pg_rewind.c:433
  13. (gdb)start
  14. Temporary breakpoint 2 at 0x4021a3: file /data/guqi/src/build_alone/../xxx/src/bin/pg_rewind/pg_rewind.c, line 125.
  15. Starting program: /data/guqi/postgres/app/bin/pg_rewind -D /data/guqi/data/master --source-server=host=127.0.0.1\ port=51101\ user=guqi
  16. [Thread debugging using libthread_db enabled]
  17. Using host libthread_db library "/usr/lib64/libthread_db.so.1".
  18. Temporary breakpoint 2, main (argc=4, argv=0x7fffffffe388)
  19. at /data/guqi/src/build_alone/../xxx/src/bin/pg_rewind/pg_rewind.c:125
  20. 125 set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_rewind"));
  21. (gdb) c
  22. Continuing.

场景二的两个方式,可以结合使用。

GDB常用的调试命令

  1. 在指定的文件,指定行,打断点
  1. b postgres.c:line_num
  1. 查看/删除断点
  1. info b
  2. delete 1-5(断点序号)
  1. 执行
  1. c:执行程序,直到断点或者结束为止
  2. n:单步执行
  3. s:单步执行,遇到函数调用,会进入函数内部
  1. 打印程序内的变量
  1. -- 这个比较多变
  2. -- 变量的形式支持类型强转,指针引用,内存地址等等,很强大
  3. p var
  1. 主动调用函数
  1. call func_name(pam_1,pam_2)
  1. 跳越到程序指定行去执行,类似goto语法
  1. -- 跳跃过去之后,程序会直接开始执行,相当于从第xx行开始敲了个continue命令
  2. jump line_num