第二课:启动 GDB 调试
使用 GDB 调试程序一般有三种方式:
- gdb filename
- gdb attach pid
- gdb filename corename
这也对应着本节课的核心内容:
- 直接调试目标程序
- 附加进程
- 调试 core 文件
接下来我们逐一讲解。
2.1 直接调试目标程序
在开发阶段或者研究别人的项目时,当编译成功生成目标二进制文件后,可以使用 gdb filename 直接启动这个程序的调试,其中 filename 是需要启动的调试程序文件名,这种方式是直接使用 GDB 启动一个程序进行调试。注意这里说的启动一个程序进行调试其实不严谨,因为实际上只是附加(attach)了一个可执行文件,并没有把程序启动起来;接着需要输入run 命令,程序才会真正的运行起来。关于 run 命令后面的课程中会详细介绍。上一课的 GDB 调试 hello_server 系列就是使用的这种方式。
假设现在有一个程序叫 fileserver,使用 gdb fileserver 附加该程序,然后使用run 命令启动该程序。如下图所示:
2.2 附加进程
在某些情况下,一个程序已经启动了,我们想调试这个程序,但是又不想重启这个程序。假设有这样一个场景,我们的聊天测试服务器程序正在运行,运行一段时间之后,发现这个聊天服务器不能接受新的客户端连接了,这时肯定是不能重启程序的,如果重启,当前程序的各种状态信息就丢失了。怎么办呢?可以使用gdb attach 进程 ID 来将 GDB 调试器附加到聊天测试服务器程序上。例如,假设聊天程序叫 chatserver,可以使用 ps 命令获取该进程的 PID,然后使用 gdb attach 就可以调试了,操作如下:
[zhangyl@iZ238vnojlyZ flamingoserver]$ ps -ef | grep chatserver
zhangyl 21462 21414 0 18:00 pts/2 00:00:00 grep --color=auto chatserver
zhangyl 26621 1 5 Oct10 ? 2-17:54:42 ./chatserver -d
实际执行如下图所示:
通过以上代码得到 chatserver 的 PID 为 26621,然后使用 gdb attach 26621 把 GDB 附加到 chatserver 进程,操作并输出如下:
[zhangyl@localhost flamingoserver]$ gdb attach 26621
Attaching to process 26661
Reading symbols from /home/zhangyl/flamingoserver/chatserver...done.
Reading symbols from /usr/lib64/mysql/libmysqlclient.so.18...Reading symbols from /usr/lib64/mysql/libmysqlclient.so.18...(no debugging symbols found)...done.
Reading symbols from /lib64/libpthread.so.0...(no debugging symbols found)...done.
[New LWP 42931]
[New LWP 42930]
[New LWP 42929]
[New LWP 42928]
[New LWP 42927]
[New LWP 42926]
[New LWP 42925]
[New LWP 42924]
[New LWP 42922]
[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...(no debugging symbols found)...done.
为了节约篇幅,上述代码中我删掉了一些无关的信息。当提示 “Attaching to process 26621” 时就说明我们已经成功地将 GDB 附加到目标进程了。需要注意的是,程序使用了一些系统库(如 libc.so),由于这是发行版本的 Linux 系统,这些库是没有调试符号的,因而 GDB 会提示找不到这些库的调试符号。因为目的是调试 chatserver,对系统 API 调用的内部实现并不关注,所以这些提示可以不用关注,只要 chatserver 这个文件有调试信息即可。
当用 gdb attach 上目标进程后,调试器会暂停下来,此时可以使用 continue 命令让程序继续运行,或者加上相应的断点再继续运行程序(这里提到的 continue 命令不熟悉也没有关系,后续会详细介绍这些命令的使用方法)。
当调试完程序想结束此次调试时,而且不对当前进程 chatserver 有任何影响,也就是说想让这个程序继续运行,可以在 GDB 的命令行界面输入 detach 命令让程序与 GDB 调试器分离,这样 chatserver 就可以继续运行了:
(gdb) detach
Detaching from program: /home/zhangyl/flamingoserver/chatserver, process 42921
然后再退出 GDB 就可以了:
(gdb) quit
[zhangyl@localhost flamingoserver]$
2.3 调试 core 文件
有时候,服务器程序运行一段时间后会突然崩溃,这并不是我们希望看到的,需要解决这个问题。只要程序在崩溃的时候有 core 文件产生,就可以使用这个 core 文件来定位崩溃的原因。当然,Linux 系统默认是不开启程序崩溃产生 core 文件这一机制的,我们可以使用 ulimit -c 命令来查看系统是否开启了这一机制。
顺便提一句,ulimit 这个命令不仅仅可以查看 core 文件生成是否开启,还可以查看其他的一些功能,比如系统允许的最大文件描述符的数量等,具体可以使用 ulimit -a 命令来查看,由于这个内容与本课主题无关,这里不再赘述。
[zhangyl@localhost flamingoserver]$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 15045
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 4096
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
发现 core file size 那一行默认是 0,表示关闭生成 core 文件,可以使用“ulimit 选项名 设置值”来修改。例如,可以将 core 文件生成改成具体某个值(最大允许的字节数),这里我们使用 ulimit -c unlimited(unlimited 是 -c 选项值)直接修改成不限制大小。
[zhangyl@localhost flamingoserver]$ ulimit -c unlimited
[zhangyl@localhost flamingoserver]$ ulimit -a
core file size (blocks, -c) unlimited
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 15045
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 4096
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
注意,这个命令容易记错,第一个 ulimit 是 Linux 命令, -c 选项后面的 unlimited 是选项的值,表示不限制大小,当然也可以改成具体的数值大小。很多初学者在学习这个命令时,总是把 ulimit 命令和 unlimited 取值搞混淆,如果读者能理解其含义,一般就不会混淆了。
还有一个问题就是,这样修改以后,当我们关闭这个 Linux 会话,设置项的值就会被还原成 0,而服务器程序一般是以后台程序(守护进程)长周期运行,也就是说当前会话虽然被关闭,服务器程序仍然继续在后台运行,这样这个程序在某个时刻崩溃后,是无法产生 core 文件的,这种情形不利于排查问题。因此,我们希望这个选项永久生效,永久生效的方式是把“ulimit -c unlimited”这一行加到 /etc/profile 文件中去,放到这个文件最后一行即可。
具体的例子
生成的 core 文件的默认命名方式是 core.pid,举个例子,比如某个程序当时运行时其进程 ID 是 16663,那么它崩溃产生的 core 文件的名称就是 core.16663。我们来看一个具体的例子,某次我发现服务器上的 msg_server 崩溃了,产生了一个如下的 core 文件:
-rw------- 1 root root 10092544 Sep 9 15:14 core.21985
就可以通过这个 core.21985 文件来排查崩溃的原因,调试 core 文件的命令是:
gdb filename corename
其中,filename 就是程序名,这里就是 msg_server;corename 是 core.21985,我们输入 gdb msg_server core.21985 来启动调试:
[root@myaliyun msg_server]# gdb msg_server core.21985
Reading symbols from /root/teamtalkserver/src/msg_server/msg_server...done.
[New LWP 21985]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Core was generated by `./msg_server -d'.
Program terminated with signal 11, Segmentation fault.
#0 0x00000000004ceb1f in std::less<CMsgConn*>::operator() (this=0x2283878, __x=@0x7ffca83563a0: 0x2284430, __y=@0x51: <error reading variable>)
at /usr/include/c++/4.8.2/bits/stl_function.h:235
235 { return __x < __y; }
可以看到程序崩溃的地方是在 stl_function.h 的第 235 行,然后通过 bt 命令(后续将详细介绍该命令)查看崩溃时的调用堆栈,进一步分析就能找到崩溃的原因。
(gdb) bt
#0 0x00000000004ceb1f in std::less<CMsgConn*>::operator() (this=0x2283878, __x=@0x7ffca83563a0: 0x2284430, __y=@0x51: <error reading variable>)
at /usr/include/c++/4.8.2/bits/stl_function.h:235
#1 0x00000000004cdd70 in std::_Rb_tree<CMsgConn*, CMsgConn*, std::_Identity<CMsgConn*>, std::less<CMsgConn*>, std::allocator<CMsgConn*> >::_M_get_insert_unique_pos
(this=0x2283878, __k=@0x7ffca83563a0: 0x2284430) at /usr/include/c++/4.8.2/bits/stl_tree.h:1324
#2 0x00000000004cd18a in std::_Rb_tree<CMsgConn*, CMsgConn*, std::_Identity<CMsgConn*>, std::less<CMsgConn*>, std::allocator<CMsgConn*> >::_M_insert_unique<CMsgConn* const&> (this=0x2283878, __v=@0x7ffca83563a0: 0x2284430) at /usr/include/c++/4.8.2/bits/stl_tree.h:1377
#3 0x00000000004cc8bd in std::set<CMsgConn*, std::less<CMsgConn*>, std::allocator<CMsgConn*> >::insert (this=0x2283878, __x=@0x7ffca83563a0: 0x2284430)
at /usr/include/c++/4.8.2/bits/stl_set.h:463
#4 0x00000000004cb011 in CImUser::AddUnValidateMsgConn (this=0x2283820, pMsgConn=0x2284430) at /root/teamtalkserver/src/msg_server/ImUser.h:42
#5 0x00000000004c64ae in CDBServConn::_HandleValidateResponse (this=0x227f6a0, pPdu=0x22860d0) at /root/teamtalkserver/src/msg_server/DBServConn.cpp:319
#6 0x00000000004c5e3d in CDBServConn::HandlePdu (this=0x227f6a0, pPdu=0x22860d0) at /root/teamtalkserver/src/msg_server/DBServConn.cpp:203
#7 0x00000000005022b3 in CImConn::OnRead (this=0x227f6a0) at /root/teamtalkserver/src/base/imconn.cpp:148
#8 0x0000000000501db3 in imconn_callback (callback_data=0x7f4b20 <g_db_server_conn_map>, msg=3 '\003', handle=8, pParam=0x0)
at /root/teamtalkserver/src/base/imconn.cpp:47
#9 0x0000000000504025 in CBaseSocket::OnRead (this=0x227f820) at /root/teamtalkserver/src/base/BaseSocket.cpp:178
#10 0x0000000000502f8a in CEventDispatch::StartDispatch (this=0x2279990, wait_timeout=100) at /root/teamtalkserver/src/base/EventDispatch.cpp:386
#11 0x00000000004fddbe in netlib_eventloop (wait_timeout=100) at /root/teamtalkserver/src/base/netlib.cpp:160
#12 0x00000000004d18c2 in main (argc=2, argv=0x7ffca8359978) at /root/teamtalkserver/src/msg_server/msg_server.cpp:213
(gdb)
堆栈 #4 就不是库代码了,我们可以排查这里的代码,然后找到问题原因。
自定义 core 文件名称
但是细心的读者会发现一个问题:一个正在程序运行时,其 PID 是可以获取到的,但是当程序崩溃后,产生了 core 文件,尤其是多个程序同时崩溃,我们根本没法通过 core 文件名称中的 PID 来区分到底是哪个服务解决这个问题有两个方法:
- 程序启动时,记录一下自己的 PID
void writePid()
{
uint32_t curPid = (uint32_t) getpid();
FILE* f = fopen("xxserver.pid", "w");
assert(f);
char szPid[32];
snprintf(szPid, sizeof(szPid), "%d", curPid);
fwrite(szPid, strlen(szPid), 1, f);
fclose(f);
}
我们在程序启动时调用上述 writePID 函数,将程序当时的 PID 记录到xxserver.pid 文件中去,这样当程序崩溃时,可以从这个文件中得到进程当时运行的 PID,这样就可以与默认的 core 文件名后面的 PID 做匹配了。
- 自定义 core 文件的名称和目录
/proc/sys/kernel/core_uses_pid
可以控制产生的 core 文件的文件名中是否添加 PID 作为扩展,如果添加则文件内容为 1,否则为 0;/proc/sys/kernel/core_pattern 可以设置格式化的 core 文件保存位置或文件名。修改方式如下:
echo "/corefile/core-%e-%p-%t" > /proc/sys/kernel/core_pattern
各个参数的说明如下:
参数名称 | 参数含义(英文) | 参数含义(中文) |
---|---|---|
%p | insert pid into filename | 添加 pid 到 core 文件名中 |
%u | insert current uid into filename | 添加当前 uid 到 core 文件名中 |
%g | insert current gid into filename | 添加当前 gid 到 core 文件名中 |
%s | insert signal that caused the coredump into the filename | 添加导致产生 core 的信号到 core 文件名中 |
%t | insert UNIX time that the coredump occurred into filename | 添加 core 文件生成时间(UNIX)到 core 文件名中 |
%h | insert hostname where the coredump happened into filename | 添加主机名到 core 文件名中 |
%e | insert coredumping executable name into filename | 添加程序名到 core 文件名中 |
假设现在的程序叫 test,我们设置该程序崩溃时的 core 文件名如下:
echo "/root/testcore/core-%e-%p-%t" > /proc/sys/kernel/core_pattern
那么最终会在 /root/testcore/ 目录下生成的 test 的 core 文件名格式如下:
-rw-------. 1 root root 409600 Jan 14 13:54 core-test-13154-1547445291
需要注意的是,您使用的用户必须对指定 core 文件目录具有写权限,否则生成时 会因为权限不足而导致无法生成 core 文件。
2.4 小结
本节课介绍了使用 GDB 调试程序的三种方式,理解并熟练使用这三种方式可以帮助读者在遇到问题时准确地选择调试方法。
第二课:启动 GDB 调试的更多相关文章
- 第02课:启动GDB调试
使用GDB调试程序一般有三种方式: gdb filename gdb attach pid dgb filename corename 也对应这本节课的核心内容: 直接调试目标程序 附加进程 调试co ...
- GDB调试笔记
参考资料:GDB调试精粹及使用实例 # 调试实例 #include <iostream> #include <cstring> using namespace std; ][] ...
- gdb调试段错误及使用
在编程调试中,经常出现段错误,此时可用gdb调试.具体方法为注册段错误信号处理函数,在处理函数中启动gdb.具体代码如下: void segv_handler(int no) { ]; ]; FILE ...
- Nginx学习之十四-GDB调试Nginx初试
本文的测试环境: Win7+虚拟机VMWareVMware-workstation-full-7.1.4-385536+Ubuntu12.04 Nginx-1.4.0 要想有效的研究Nginx源码,必 ...
- Linux下GDB调试简单示例
这里介绍对文件first.c的基本GDB调试操作,只有部分命令,只是一个示例,运行环境为装有gcc编译器和gdb调试器的Linux环境,基本GDB调试命令如下表: 命令 ...
- GDB调试——常用的命令
首先说明一点,如果我们要使用GDB来调试我们的C/C++程序时,在使用GCC编译程序时,应该带上 –g 参数, 它负责生成 与GDB相关的调试信息: 1.如何对一个文件启动GDB调试? 方法一: 命令 ...
- 使用gdb调试
启用gdb进行调试二进制程序,必须在二进制程序在采用gcc或g++编译时加入-g参数 启动gdb进行调试的几种形式: 直接启动gdb程序进行调试program程序 gdb program 启动gdb挂 ...
- gdb调试快速入门
编译指令 gcc test.c -o test -g -g是加入调试信息,加入源码信息 启动gdb调试 gdb test 进入gdb中 设置参数 set args 10 20 显示参数show age ...
- GDB调试指南-启动调试
前言 GDB(GNU Debugger)是UNIX及UNIX-like下的强大调试工具,可以调试ada, c, c++, asm, minimal, d, fortran, objective-c, ...
随机推荐
- 一文带你认识LPWA通信技术
摘要:为了满足越来越多的远距离物联网设备的连接需求,LPWA应用而生. 本文分享自华为云社区<常见物联网通信技术之LPWA通信技术>,作者:爱吃面包的猫. 如果你比较关注物联网圈的话,想必 ...
- SpringMVC 源码解析笔记
作者笔记仓库:https://github.com/seazean/javanotes 欢迎各位关注我的笔记仓库,clone 仓库到本地后使用 Typora 阅读效果更好. 一.调度函数 请求进入原生 ...
- python打包exe之pyinstaller用法
pyinstaller可以将python写好的脚本打包成exe文件,方便windows用户在没有python环境下运行.这个程序完全跨平台,包括Windows.Linux.Mac OS X等多个操作系 ...
- 运行第一个程序!hello world!
第一个程序:1 //打印 hello world 2 3 #include <stdio.h> 4 int main() 5 { 6 printf("hello world!\n ...
- Bypass D盾 Webshell
测试环境 OS: Windows Server 2008 PHP: PHP 7.2.10 D盾: d_safe_2.1.5.4 绕过分析 我是以 eval($_POST['x']); 这个代码以根据, ...
- Semi-automation Script Based on Sleep
The following script auto login to server 49, send 2 commands and exit from the server. Create a aut ...
- 1002 A+B for Polynomials (25分) 格式错误
算法笔记上能踩的坑都踩了. #include<iostream> using namespace std; float a[1001];//至少1000个位置 int main(){ in ...
- 基于RT1052 Aworks 内存扩容记录(一)
本文主要是通过迁移的思维,记录本人初次使用周立功的Aworks框架进行BSP开发 1. 首先阅读原理图 内存容量由32M扩容至64M. 2. 再则比较两颗芯片的参数 通过比较32M和64M SDRAM ...
- win7环境下配置JDK&&安装Weblogic12.2.1.4.0
win7环境下安装Weblogic12.2.1.4.0 写在前面 最近因为想复现一下weblogic的CVE-2020-2555和CVE-2020-2883漏洞,需要weblogic环境,但是vulh ...
- ECDSA—模乘模块
如果a,b属于GF(P),则有乘法运算a*b=r (mod p), 其中r满足0<r<p-1,即a*b除以p的余数.该操作成为模p乘法.本模块输入两个数,完成两个数的模乘运算. 信号名 方 ...