文章目录
-
- 一、前言
- 二、环境依赖
- 三、Helloword 示例
- 四、调试 Redis 示例
-
- 1、下载 Redis 源码并解压
- 确认编译选项
- 3、检查编译
- 4、GDB 调用 redis-server
-
- 4.1、方法一
- 4.2、方法二
- 5、视频示例
- 6、GDB 调试过程
- 五、总结
一、前言
前几天遇到一个 redis 的问题。
说复杂并不复杂,但我想在重现时下载 redis 编译什么的源码折腾了一遍。
调试工具也用于这个过程GDB。
我以为这个工具已经写过文章了,但是我查了自己的微信官方账号和博客,没有。
我现在有了一个习惯。当我不能进行性能分析时,首先检查我的官方账户和博客,我通常会找到答案
二、环境依赖
因为前几天遇到了一个 redis 所以编译了问题 reids,并做一些监控。在这里记录一些操作实践。如果有必要,我会再做一次 GDB 的一些命令使用整理一下。
首先,GDB 对环境有一些要求,debug 包必须完整。例如,我的主机上装有以下内容 debug 包。
[root@7dgroup redis]# rpm -qa|grep debug gcc-base-debuginfo-4.8.5-4.el7.x86_64 glibc-debuginfo-common-2.17-157.el7_3.1.x86_64 nss-softokn-debuginfo-3.28.3-8.el7_4.x86_64 ncurses-debuginfo-5.9-13.20130511.el7.x86_64 yum-plugin-auto-update-debug-info-1.1.31-50.el7.noarch procps-ng-debuginfo-3.3.10-5.el7_2.x86_64 kernel-debuginfo-3.10.0-327.28.2.el7.x86_64 net-snmp-debuginfo-5.7.2-33.el7_5.2.x86_64 gcc-debuginfo-4.8.5-4.el7.x86_64 zlib-debuginfo-1.2.7-15.el7.x86_64 pcre-debuginfo-8.32-15.el7_2.1.x86_64 systemd-debuginfo-219-19.el7_2.12.x86_64 glibc-debuginfo-2.17-157.el7_3.1.x86_64 kernel-debuginfo-common-x86_64-3.10.0-327.28.2.el7.x86_64 [root@7dgroup redis]#
建议大家不要自己安装各种包依赖,比如这样安装:
[root@7dgroup ]# yum install gdb [root@7dgroup ]# debuginfo-install libgcc-4.8.5-4.el7.x86_64 [root@7dgroup ]# debuginfo-install nss-softokn-freebl-3.28.3-8.el7_4.x86_64 pcre-8.32-15.el7_2.1.x86_64 zlib-1.2.7-15.el7.x86_64
所有的依赖都会给你添加。
三、Helloword 示例
先看一个例子:
性能分析调试工具——GDB(你认为性能分析不能用吗?
人越老,越觉得 helloword 示例的好处。直观简单。
GDB 是老牌的调试工具。
在上面的例子中,我写了一个死循环代码,执行左窗,调试右窗。
以前经常看到有人用 GDB 调试 coredump 文件。
当然 GDB 也可以调试正在运行的程序,但是这里有一个前提,就是编译的时候加上 -g 的参数。
通过 attach 连进去后,可以做很多动作,但要知道 attach 同时也会导致程序暂停。如果您想继续运行,请输入c(continue)。
GDB 调试要求为静态状态。
当然,现在有很多动态调试的方法。如果有必要,我以后再写。
attach 之后,您可以查看线程信息 info threads。
输入 bt 查看当前程序运行到哪里。
调试 coredump 文件的优点是程序已经 crash 此时直接 bt 就知道 crash 在哪里?但也需要添加到编译中。-g的参数的。
此外,调试要求系统中有 debuginfo 支持。这个版本必须对应于系统的核心版本,否则就不可用了。
GDB 支持设置断点、观察点和捕获点。还有检查堆栈、运行数据、源代码等的命令。
本来以为可以写很多示例命令,但是写的时候感觉像是在写操作手册。
操作手册已经很多了,所以了。如果有人感兴趣,我会根据读者的要求写相应的例子。
我们在性能分析中经常用的的场景是:
- 查看断了哪里,然后跟着堆栈去找代码。
- 在函数或变量上设置断点,执行程序,并在断点命中时查看堆栈信息。
动态调试工具也写在后面,像 systemtap 之类的。
在性能分析过程中,一些似乎不是性能工程师所需要的工具经常被使用。我经常听到一些做性能的人认为这不是他们应该做的。或者根据我以前的观点,职位可以有限,但不要限制你的能力。
但是,不要学偏了。技术需要一条主线,所有的能力都会提供主线服务,否则你不知道该怎么办。
回到性能分析。一些初学者或有经验的人经常与测试工具竞争。
四、调试 Redis 示例
1、下载 Redis 源码并解压
[root@7dgroup GDB]# wget wget http://download.redis.io/releases/redis-5.0.5.tar.gz --2019-08-04 10:15:14-- http://wget/ 主机正在分析中 wget (wget)... 失败:未知名称或服务。 wget: 无法分析主机地址 “wget” -2019-08-04 10:15:14-- http://download.redis.io/releases/redis-5.0.5.tar.gz
正在解析主机 download.redis.io (download.redis.io)... 109.74.203.151
正在连接 download.redis.io (download.redis.io)|109.74.203.151|:80... 已连接。
已发出 HTTP 请求,正在等待回应... 200 OK
长度:1975750 (1.9M) [application/x-gzip]
正在保存至: “redis-5.0.5.tar.gz”
100%[=======================================================================================================================================================>] 1,975,750 20.2KB/s 用时 1m 47s
2019-08-04 10:17:02 (18.0 KB/s) - 已保存 “redis-5.0.5.tar.gz” [1975750/1975750])
FINISHED --2019-08-04 10:17:02--
Total wall clock time: 1m 48s
Downloaded: 1 files, 1.9M in 1m 47s (18.0 KB/s)
[root@7dgroup GDB]# tar zxvf redis-5.0.5.tar.gz
[root@7dgroup GDB]# cd redis-5.0.5
[root@7dgroup redis-5.0.5]# tree -h
.
├── [104K] 00-RELEASENOTES
├── [ 53] BUGS
......
......
......
├── [3.7K] speed-regression.tcl
└── [ 693] whatisdoing.sh
68 directories, 725 files
[root@7dgroup redis-5.0.5]#
2、确认编译选项
[root@7dgroup redis-5.0.5]# grep DEBUG src/Makefile
FINAL_CFLAGS=$(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS)
FINAL_LDFLAGS=$(LDFLAGS) $(REDIS_LDFLAGS) $(DEBUG)
DEBUG=-g -ggdb
DEBUG=-g
DEBUG_FLAGS=-g
export CFLAGS LDFLAGS DEBUG DEBUG_FLAGS
[root@7dgroup redis-5.0.5]#
可以看到默认 redis 源码中就已经把 debug 选项配置好了,真是贴心。
[root@7dgroup redis-5.0.5]# make PREFIX=/root/GDB/redis-5.0.5/redis install
cd src && make install
make[1]: 进入目录“/root/GDB/redis-5.0.5/src”
CC Makefile.dep
make[1]: 离开目录“/root/GDB/redis-5.0.5/src”
make[1]: 进入目录“/root/GDB/redis-5.0.5/src”
rm -rf redis-server redis-sentinel redis-cli redis-benchmark redis-check-rdb redis-check-aof *.o *.gcda *.gcno *.gcov redis.info lcov-html Makefile.dep dict-benchmark
(cd ../deps && make distclean)
make[2]: 进入目录“/root/GDB/redis-5.0.5/deps”
......
......
......
(cd hiredis && make clean) > /dev/null || true
Hint: It's a good idea to run 'make test' ;)
INSTALL install
INSTALL install
INSTALL install
INSTALL install
INSTALL install
make[1]: 离开目录“/root/GDB/redis-5.0.5/src”
请注意,redis 默认使用了 -O2 的编译优化选项。可以去掉,这个在官方的文档中有说明。所以这里的编译最好用如下命令:
make noopt PREFIX=/root/GDB/redis-5.0.5/redis install
另外,这里我也指定了安装目录。
3、检查编译
[root@7dgroup redis-5.0.5]# make test
cd src && make test
make[1]: 进入目录“/root/GDB/redis-5.0.5/src”
CC Makefile.dep
make[1]: 离开目录“/root/GDB/redis-5.0.5/src”
make[1]: 进入目录“/root/GDB/redis-5.0.5/src”
Cleanup: may take some time... OK
Starting test server at port 11111
[ready]: 28175
Testing unit/printver
[ready]: 28177
Testing unit/dump
[ready]: 28180
Testing unit/auth
......
......
......
o/ All tests passed without errors!
Cleanup: may take some time... OK
make[1]: 离开目录“/root/GDB/redis-5.0.5/src”
[root@7dgroup redis-5.0.5]#
全部通过没有错误。
以上为什么把 redis 的编译列这么清楚呢。主要是如果有些人在过程中遇到的杂七杂八的问题,可以有个参照。
4、GDB 调用 redis-server
4.1、方法一
直接通过 GDB 启动。
[root@7dgroup redis]# gdb bin/redis-server
.......
.......
.......
Reading symbols from /root/GDB/redis-5.0.5/redis/bin/redis-server...done.
(gdb)
4.2、方法二
- 先启动 redis-server。
[root@7dgroup redis]# ./bin/redis-server &
[1] 4001
[root@7dgroup redis]# 4001:C 04 Aug 2019 11:01:39.195 # oO0OoO0Oo bit
......
.-`` .-```. ```\/ _.,_ ''-._ ( ' , .-` | `, ) Running in standalone mode |`-._`-...-` __...-.``-._|'` _.-'| Port: 6379 | `-._ `._ / _.-' | PID: 4001
`-._ `-._ `-./ _.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
......
4001:M 04 Aug 2019 11:01:39.196 * Ready to accept connections
- 查 redis 的 pid
[root@7dgroup redis]# ps -ef|grep redis
root 4001 742 0 11:01 pts/3 00:00:00 ./bin/redis-server *:6379
root 4014 742 0 11:01 pts/3 00:00:00 grep --color=auto redis
[root@7dgroup redis]#
- 连上 GDB
[root@7dgroup redis]# gdb attach 4001
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-80.el7
......
Loaded symbols for /lib64/libdl.so.2
Reading symbols from /lib64/librt.so.1...Reading symbols from /usr/lib/debug/usr/lib64/librt-2.17.so.debug...done.
done.
Loaded symbols for /lib64/librt.so.1
Reading symbols from /lib64/libpthread.so.0...Reading symbols from /usr/lib/debug/usr/lib64/libpthread-2.17.so.debug...done.
done.
[New LWP 4004]
[New LWP 4003]
[New LWP 4002]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Loaded symbols for /lib64/libpthread.so.0
Reading symbols from /lib64/libc.so.6...Reading symbols from /usr/lib/debug/usr/lib64/libc-2.17.so.debug...done.
done.
Loaded symbols for /lib64/libc.so.6
Reading symbols from /lib64/ld-linux-x86-64.so.2...Reading symbols from /usr/lib/debug/usr/lib64/ld-2.17.so.debug...done.
done.
Loaded symbols for /lib64/ld-linux-x86-64.so.2
0x00007f8a60ce5d13 in epoll_wait () at ../sysdeps/unix/syscall-template.S:81
81 T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
(gdb)
或者这样:
[root@7dgroup redis]# gdb ./bin/redis-server 4001
......
Reading symbols from /root/GDB/redis-5.0.5/redis/bin/redis-server...done.
Attaching to program: /root/GDB/redis-5.0.5/redis/./bin/redis-server, process 4001
Reading symbols from /lib64/libm.so.6...Reading symbols from /usr/lib/debug/usr/lib64/libm-2.17.so.debug...done.
done.
Loaded symbols for /lib64/libm.so.6
Reading symbols from /lib64/libdl.so.2...Reading symbols from /usr/lib/debug/usr/lib64/libdl-2.17.so.debug...done.
done.
Loaded symbols for /lib64/libdl.so.2
Reading symbols from /lib64/librt.so.1...Reading symbols from /usr/lib/debug/usr/lib64/librt-2.17.so.debug...done.
done.
Loaded symbols for /lib64/librt.so.1
Reading symbols from /lib64/libpthread.so.0...Reading symbols from /usr/lib/debug/usr/lib64/libpthread-2.17.so.debug...done.
done.
[New LWP 4004]
[New LWP 4003]
[New LWP 4002]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Loaded symbols for /lib64/libpthread.so.0
Reading symbols from /lib64/libc.so.6...Reading symbols from /usr/lib/debug/usr/lib64/libc-2.17.so.debug...done.
done.
Loaded symbols for /lib64/libc.so.6
Reading symbols from /lib64/ld-linux-x86-64.so.2...Reading symbols from /usr/lib/debug/usr/lib64/ld-2.17.so.debug...done.
done.
Loaded symbols for /lib64/ld-linux-x86-64.so.2
0x00007f8a60ce5d13 in epoll_wait () at ../sysdeps/unix/syscall-template.S:81
81 T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
(gdb)
请大家注意,symbols 的加载最好不要出现任务警告或错误,不然调试过程中遇到搞不清楚的问题。
这些 symbols 也是因为有了前面的 debug 编译选项才产生的。并且官方说不影响性能。
下面就可以调试了。
5、视频示例
性能分析之调试工具——GDB之二
DEMO 给人直观的感觉。在上面的 demo 中,我用了 -tui 参数,在调试时,把代码窗口也显示出来,这样就知道当前执行到了哪行代码。
如果你在使用时没有显示出源码,则需要用directory命令把源码加载进来。
命令如下:
(gdb) directory /root/GDB/redis-5.0.5/src
Source directories searched: /root/GDB/redis-5.0.5/src:$cdir:$cwd
(gdb)
6、GDB 调试过程
- 设置断点
(gdb) b setCommand
Breakpoint 1 at 0x452c80: file t_string.c, line 96.
(gdb)
- 继续执行
(gdb) c
Continuing.
Breakpoint 1, setCommand (c=0x7f8a6070da40) at t_string.c:96
96 void setCommand(client *c) {
- 断点命中后查看线程
(gdb) info threads
Id Target Id Frame
4 Thread 0x7f8a59cd6700 (LWP 4002) "redis-server" pthread_cond_wait@@GLIBC_2.3.2 ()
at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S:185
3 Thread 0x7f8a594d5700 (LWP 4003) "redis-server" pthread_cond_wait@@GLIBC_2.3.2 ()
at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S:185
2 Thread 0x7f8a58cd4700 (LWP 4004) "redis-server" pthread_cond_wait@@GLIBC_2.3.2 ()
at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S:185
* 1 Thread 0x7f8a61ae3f80 (LWP 4001) "redis-server" setCommand (c=0x7f8a6070da40)
at t_string.c:96
(gdb)
线程号前面的*代表是当前正在执行的线程。
- 切换到线程中去
(gdb) thread 1
[Switching to thread 1 (Thread 0x7f8a61ae3f80 (LWP 4001))]
#0 setCommand (c=0x7f8a6070da40) at t_string.c:96
96 void setCommand(client *c) {
(gdb)
切换到thread 1。
- 查看断点
查看断点:
(gdb) bt
#0 setCommand (c=0x7f8a6070da40) at t_string.c:96
#1 0x0000000000430ac7 in call (c=c@entry=0x7f8a6070da40, flags=flags@entry=15)
at server.c:2439
#2 0x0000000000431d5f in processCommand (c=0x7f8a6070da40) at server.c:2733
#3 0x0000000000440a55 in processInputBuffer (c=0x7f8a6070da40) at networking.c:1470
#4 0x000000000042af80 in aeProcessEvents (eventLoop=eventLoop@entry=0x7f8a6062b0a0,
flags=flags@entry=11) at ae.c:443
#5 0x000000000042b24b in aeMain (eventLoop=0x7f8a6062b0a0) at ae.c:501
#6 0x00000000004280ff in main (argc=<optimized out>, argv=0x7ffe1bf60ac8) at server.c:4200
(gdb)
- 单步跟踪 next
(gdb) n
102 for (j = 3; j < c->argc; j++) {
- 进入到函数内部 step
(gdb) s
100 int flags = OBJ_SET_NO_FLAGS;
(gdb)
- 打印变量值
(gdb) p j
$3 = 3
(gdb) p *a
$6 = 58989
(gdb)
(gdb) p 't_string.c'::a
$7 = {
58989, 57068, 5}
(gdb) i locals
j = 3
expire = 0x0
unit = 0
flags = 0
(gdb)
指定查看全局变量的值,通过 ::
操作符。
file::variable
function::variable
因为当全局变量与局部变量冲突时,全局变量会被隐藏。
- 查看当前
stack frame
局部变量
(gdb) i locals
j = 3
expire = 0x0
unit = 0
flags = 0
(gdb)
- 查看当前
stack frame
参数
(gdb) info args
c = 0x7f8a6070da40
(gdb)
- 修改变量的值
(gdb) i locals
j = 0
expire = 0x5d46a88f
unit = 0
flags = 4392628
(gdb) set var j = 1
(gdb) i locals
j = 1
expire = 0x5d46a88f
unit = 0
flags = 4392628
(gdb)
上面这些操作让大家有一个直观的认识,看到了完整的调试过程。但是并不是 GDB 所有的指令集。
至少有了一个感觉就是我们在调试时对程序是想干吗干吗。
五、总结
我看到有挺多的 GDB 的指令集的教程,有兴趣的可以一一试下指令。
本来我也是整理了指令集的,但是感觉和其他人整理的也没有什么区别,所以就不想发出来了。
后面有的 GDB 调试的具体场景,再看 GDB 在具体场景中的使用。