石家庄网站托管公司,江苏省建设工程竣工备案网站,企业年金怎么领取最划算,免费网页转app软件前言
在完成了整个项目后#xff0c;我用make命令编译了server#xff0c;当我运行./server文件时#xff0c;出现了段错误 在大量的代码中找出错因并不是一件容易的事#xff0c;尤其是对新手程序员来说。而寻找bug的过程就像是侦探调查线索追查凶手一样#xff0c;我们…前言
在完成了整个项目后我用make命令编译了server当我运行./server文件时出现了段错误 在大量的代码中找出错因并不是一件容易的事尤其是对新手程序员来说。而寻找bug的过程就像是侦探调查线索追查凶手一样我们要通过一点一点的蛛丝马迹来剥离表象找到真凶。
今天就由我来扮演一次侦探调查一番这个段错误到底出自谁手。 段错误我们面临的是什么敌人
在解决问题之前让我先来了解一下什么是段错误 Segmentation fault (core dumped) 是一种程序运行时错误通常表示程序试图访问无效的内存地址。这种错误可能由多种原因引起包括指针错误、数组越界、使用已释放的内存等。 一般情况下解决段错误的方法是使用gdb调试段错误生成的core文件。但是许多人会发现系统不会生成core dump文件这是因为 core dump文件是由linux系统进行生成而且其往往较大默认情况下Linux是不允许生成core dump文件的。
我们可以使用
ulimit -c
命令来查看如果是 0说明Linux允许的core文件最大大小为0即不允许生成core文件
这时我们要使用
ulimit -c unlimited
来将core文件最大大小改为无限。
我们再编译运行程序就会产生core文件但是默认的core文件生成目录不在本目录因此需要把默认core文件生成目录改成运行目录再然后.....
...... 这也太麻烦了有没有更简单的方法。哎你别说还真有接下来我们不借助core文件来查案 GDB调试探方向段错误真相初现端倪
首先我们在makefile文件里的编译代码加上-g的可选项这样生成的server就是一个可以用gdb调试的可执行文件。 随后我们使用gdb命令进入server可执行文件
gdb server
进入gdb调试器以后我们使用run运行
gdb给出已下报错 注意看这里的MYSQL Error: mysql_real_connect虽然gdb告诉我们是在 iofputs.c 的__GI__IO_fputs函数出现的段错误但是这是系统调用无数人用了那么多年不太可能出现错误所有应该还是我们自己的代码有问题。那我们该从何找起呢
哎我们发现这里有一条日志
MySQL Error : mysql_real_connect\n
这不是我们写的日志吗太好了我们终于发现了错误的蛛丝马迹。 深入代码危险区巧设监控锁暗敌
经过我们的重重调查我们终于定位到了错误代码函数所在地即sql_connection_pool.cpp的init函数
void connection_pool::init(string url, string User, string PassWord, string DBName, int MaxConn, int Port, int close_log) {m_url url;m_Port Port;m_User User;m_PassWord PassWord;m_DatabaseName DBName;m_close_log close_log;for (int i 0; i MaxConn; i) {MYSQL *con NULL;con mysql_init(con);if (con nullptr) {LOG_ERROR(MySQL Error : mysql_init);exit(1);}/*真正的连接函数*/con mysql_real_connect(con, url.c_str(), User.c_str(), PassWord.c_str(), DBName.c_str(), Port, NULL, 0);if (con nullptr) {LOG_ERROR(MySQL Error : mysql_real_connect);exit(1);}connList.push_back(con);m_FreeConn;}reserve sem(m_FreeConn);//信号量记录共享资源总量m_MaxConn m_FreeConn;
} 其中GDB告诉我们的线索即是这里的”人证“给出的 , 即第22行的
LOG_ERROR(MySQL Error : mysql_real_connect); 让我们看看这个函数里的关键APImysql_init和mysql_real_connect
mysql_init 是 MySQL C API 中的一个函数用于初始化和分配一个 MYSQL 结构这个结构是用于表示 MySQL 连接的句柄。这个函数通常是在开始使用 MySQL C API 之前调用的以确保连接句柄是有效的。
mysql_real_connect 函数是 MySQL C API 中的一个关键函数用于建立与 MySQL 服务器的连接
可见这个函数是通过mysql_init创建句柄再用该句柄创建mysql连接 咋一看好像这一块的代码都没有问题那是怎么回事了 为了深一步调查我们使用时空回溯大法我们在这里设下”监控“重现当时的”犯罪场景“那么我们该如何设下监控呢其实很简答就是我们在c中常用debug方法在程序中打印出调试信息这里我们就在第19行代码
con mysql_real_connect(con, url.c_str(), User.c_str(), PassWord.c_str(), DBName.c_str(), Port, NULL, 0);
的前后分别写上
coutbefore mysql_real_connectendl;
con mysql_real_connect(con, url.c_str(), User.c_str(), PassWord.c_str(), DBName.c_str(), Port, NULL, 0);
coutafter mysql_real_connectendl; 然后我们再编译运行出现以下结果 可以看到程序并非是没进入mysql_real_connect就报错而是循环了无数遍后才出现的报错这是怎么回事了为了清晰地看到循环次数我们为“监控”加上计数器 cout第i次 before mysql_real_connectendl;
con mysql_real_connect(con, url.c_str(), User.c_str(), PassWord.c_str(), DBName.c_str(), Port, NULL, 0);
cout第i次 after mysql_real_connectendl; 可以看到程序循环了151次后就结束了随后发生了段错误。这是什么原因让我们接着“调查”。 日夜辗转寻真相辛勤探寻不负望
经过前面的“调查”我们得到了线索再init循环中当程序循环151次第152次就发生了段错误。没办法我们先去调查一下是谁调用了init
我们发现调用该函数的是WebServer.cpp的void sql_pool()函数而且该函数给init的循环上限MaxConn设置的是3306
void WebServer::sql_pool() {/*初始化数据库连接池*/m_connPool connection_pool::Getinstance();m_connPool-init(localhost, m_user, m_passWord, m_databaseName, 3306, m_sql_num, m_close_log);/*初始化数据库读取表*/users-initmysql_result(m_connPool);
}
我们对比一下m_connPool的定义发现了问题所在
void connection_pool::init(string url, string User, string PassWord, string DBName, int MaxConn, int Port,int close_log)
原来是因为Port和MaxConn在定义在前的为端口定义在后的为循环上限了导致了端口号被用来当循环的上限而数据库最大能创建的连接数是152超出了152自然就触发了段错误那么我们将调用init的地方改过来即可。 后记
经过不懈的努力我终于是解决了这个BUG。但其实在解决bug的过程中我并不像文章中说的那么容易包括最开始我为了去得到 core文件找了许多方法也花了很多时间但是一直没有什么显著效果。中间我一度沮丧到想哭。最后也是不管三七二十一用GDB运行了一次server文件才找到了一点点蛛丝马迹。而后面在发现循环151次后就会段错误的时候我也一度找错了方向找了许多方法把数据库的连接上限改成3500系统能容纳的最大文件描述符也被我改成了3500如下图 但是当我以为解决问题的时候再运行虽然超过了151但在1000多的时候还是会段错误当时我想了好多办法但一直收效见微。可见在错误的方向上你越努力错的就越离谱。最后还是检查源码的时候发现这里的调用把port和Maxconn写错位了也算是给写这篇文章的大家一个警醒吧。
像这样的BUG我在项目中遇到不止一个其实项目我在两天前就写完了这两天一直在debug几乎可以说是不吃不喝地程度了连上厕所睡觉都在想怎么debug。皇天不负有心人项目我也终于是完成了后面把剩下的博客写完我的将近30天Webserver之旅就到此为止了。