前言
C++聊天服务器项目加上数据库连接池时出了bug。可我不会在Linux环境下调试,浪费了很多时间。虽然最后将这个bug解决了,但是这给了我一个警钟:必须要会GDB的简单使用
我将复现bug,使用GDB一步步地将bug揪出来。
其实这个bug,我事后回想了下,并不离谱,稍微推理下就应该知道大概在哪里出了岔子。
1、bug 复现
mysql Commands out of sync; you can't run this command now
数据库连接池在并发环境下,执行select后出错(用户登录)(insert随便用)(第一条select就报错)。当时的解决方向跑到了数据库的锁(被网上搜到的指偏了):读的时候对表加了独占锁。
现在给出不需要调试的找bug思路:第一条select就报错,但是在此之前数据库并没有读表!所以大概率不是锁的问题,而是登录模块出了问题。
当时的bug是这样的:处理登录功能会跳转到usermodel.cpp
里面执行这个函数(未修改版):
// 根据用户号码查询用户信息
User UserModel::query(int id)
{
// 1.组装sql语句
char sql[1024] = {0};
sprintf(sql, "select * from user where id = %d", id);
MySQL mysql;
MYSQL_RES *res = mysql.query(sql);
if (res != nullptr)
{
MYSQL_ROW row = mysql_fetch_row(res);
if (row != nullptr)
{
User user;
user.setId(atoi(row[0]));
user.setName(row[1]);
user.setPwd(row[2]);
user.setState(row[3]);
mysql_free_result(res);
return user;
}
}
return User();
}
我是在已经写好的代码上改的,向线程池申请资源时不需要再连接,所以我把所有的connect()删了,但是这个角落的删了连接、且不是向池申请,所以是空连接。
ConnectionPool* cp = ConnectionPool::getConnectionPool();
shared_ptr<MySQL>mysql = cp->getConnection();
MYSQL_RES *res = mysql->query(sql);
那么,如果我当时会gdb调试的话,情况会怎样呢?
2、GDB
设置断点
b chatservice.cpp:67
之后使用 run
运行到断点,再 n/s/finish
交替看运行过程。
比cout加ctrl点击还是要方便许多的。可以看到执行过程,将排查范围精准下来。
2024.2.26 更新
简单使用
ctrl+g显示行号 也可以 :set nu
b打断点
p打印,如果是打印表达式,表达式会被执行
set var可以改变变量的值,如循环把i的值改变 set var i = 8,它只是改变变量,比如本来要循环10次,把i改大后,循环次数会变少。
调试core文件
错误代码如下
#include <cstring>
#include <iostream>
using namespace std;
void bb(const int bh, const string xm)
{
char* ptr = nullptr;
*ptr = 3;
//strcpy(ptr,xm.c_str());
}
void aa(const int no, const string name)
{
bb(3, "冰冰");
}
int main()
{
aa(8, "西施");
return 0;
}
出现段错误后无法定位。Linux缺省不会生成core文件,需要修改系统参数。
先用ulimit -a查看当前用户的资源限制参数
[wsm@localhost app]$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
...
看到core文件大小默认为0,我们要把它改为不限制,core的选项是-c,所以我们用-c
[wsm@localhost app]$ ulimit -c unlimited
[wsm@localhost app]$ ulimit -a
core file size (blocks, -c) unlimited
...
再运行程序可以产生core文件
[wsm@localhost app]$ ./t2
段错误(吐核)
[wsm@localhost app]$ ls
core.28925 demo demo01.cpp t1 t1.cpp t2 t2.cpp
然后运行gdb 程序名 core文件名
[wsm@localhost app]$ gdb t2 core.28925
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-120.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/wsm/test/app/t2...done.
[New LWP 28925]
Core was generated by `./t2'.
Program terminated with signal 11, Segmentation fault.
#0 0x0000000000400869 in bb (bh=3, xm=...) at t2.cpp:8
8 *ptr = 3;
Missing separate debuginfos, use: debuginfo-install glibc-2.17-326.el7_9.x86_64 libgcc-4.8.5-44.el7.x86_64 libstdc++-4.8.5-44.el7.x86_64
可以看到是第8行产生的段错误。
可以用bt查看函数调用栈
(gdb) bt
#0 0x0000000000400869 in bb (bh=3, xm="冰冰") at t2.cpp:8
#1 0x00000000004008b1 in aa (no=8, name="西施") at t2.cpp:14
#2 0x0000000000400927 in main () at t2.cpp:19
调试正在运行中的程序
gdb 程序名 -p 进程编号
[wsm@localhost app]$ ps -ef | grep t3
wsm 29177 29058 0 20:45 pts/1 00:00:00 ./t3
wsm 29231 2903 0 20:45 pts/0 00:00:00 grep --color=auto t3
[wsm@localhost app]$ gdb t3 -p 29177
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-120.el7
...
开始调试后正在运行的程序会停住,调试结束后程序继续运行。调试如果改变了某一变量的值,结束后这个变量不会复原。
调试过程中将ii赋值为999
ii=32
ii=33
ii=999
ii=1000