标签 调试 下的文章

都是病毒惹得祸

上周日下午,接到同事的一个寻求支持的电话,原来是部门以前给中国联通做的一个运行在PC服务器上的程序在每天凌晨出现'挂死'情况,导致程序运行中断,问题连续几天复现。程序是老程序,在不下十多个省运行,一直都很稳定。通过联通的人发过来的截图,很难定位问题所在,所以只能打车到了联通机房现场查看了。

还是那句话,维护别人的又是自己不熟悉的程序那真是痛苦的,好久都不在Windows上写程序、调程序了,API都需要现到网上查。由于程序一直在现网运行,即使到了现场也依然只能从外围来看,把配置信息和一些现网数据拿到自己的Windows环境下进行模拟测试,看是否能够重现问题,可无论如何模拟都不能重现问题。

问题出在源代码中一处调用DeleteFile的地方,在凌晨那个时刻,DeleteFile总返回失败。微软的帮助文档给出了DeleteFile失败的一些原因,比如文件是只读的、文件是受保护的系统文件或者用户没有删除这个文件的权限等等。我们重点检查了那个出问题的文件夹中是否有特殊文件,将Windows设置成显示所有文件,包括隐藏文件后,依然没有发现。由于是现网主机,不便过多操作。

程序有个缺点就是没有后台日志输出,也许当初开发这个程序的同事也许开发惯了GUI的程序,没有意识到这应该是一个服务器端程序,居然在出错的时候弹出对话框,试想这个24 x 7小时运行的程序谁会眼睛一直盯着它和它交互呢,呵呵。这也是在出错的时候导致挂起的直接原因。

但是导致DeleteFile失败的深层原因还需要继续查找。经过和联通工作人员商量,决定做一次升级,增加后台日志,以便查到'幕后真凶'。

像联通这种效率不高的公司,做一个小小的升级走的流程都要耽误几天。这不昨晚才把升级程序替上去。上午我们技术支持人员将后台日志发给了我,打开一看居然是一个叫'autorun.inf'的文件导致的删除失败,通过FormatMessage和GetLastError配合得到的原因是"拒绝访问",显然是这个文件的权限很高,即使用管理员权限也无法删除,甚至我们在屏幕上根本看不到这个文件的存在,只是通过Win32 API才能找到这个文件。这时我们的技术支持发来信息说:在网上查了一下,autorun.inf可能是病毒或者是木马;一句话点醒梦中人啊,我也在网上搜索了一下,的确这个autorun.inf是病毒的产物。这时我的同事又发过来一条信息说:联通人员确认过了他们的这台PC服务器居然一直在'裸奔',就是没有安装任何防毒软件。我晕!

对这些运营商我就不再做太多评价了,地球人都知道。

通过这次事件我们也可以看到:实际软件运行时产生的问题真是多种多样,防不胜防啊。其实不考虑其他原因,我们的软件本身如果做的更好些的话,也是可以避免上述问题的发生的,细节我就不说了。

一个很有意思的Bug

这个Bug源于昨天凌晨的一次版本升级失败。睡了一大觉后,下午回到公司,重现了这个问题并找到了原因,发现这的确是一个'很有意思的Bug'。

系统在从数据库初始化过程中遇到了问题:在读取数据库数据时,提示ORA-24373错误。手册上对ORA-24373的解释是这样的:
ORA-24373: invalid length specified for statement
Cause: The length specified for the statement is either 0 or too large.
Action: Specify a valid length for the statement.

从错误的提示来看,不应该是数据的问题,但是出于对自己程序的信任,还是首先比对了数据,结果证明了数据是无误的;

看来一定是代码的问题了。首先定位到了返回错误的那个接口OCIStmtPrepare(原型: sword OCIStmtPrepare(OCIStmt *stmtp, OCIError *errhp, OraText *stmt, ub4 stmt_len, ub4 language, ub4 mode)),可是怎么看这块也不该出问题。但从ORA-24373的Cause来看,似乎是传
给OCIStmtPrepare的值不太对劲儿。

我们从数据库读取数据的一般流程:
1、获取符合某一特定条件的某一类数据的条数count;
2、读取count条数据From DB。

为了达到上面的两个目的,我们执行了两次sql操作,分别由两个语句完成,这里不妨成为sql1和sql2。sql1和sql2的内容是在代码里形成的。

其分为两部分:select的字段list和where条件部分;由于where条件部分是动态的,所以每次sql操作之前都是需要先生成sql1和sql2的。

保守的我们认为sql语句的长度是有限的,在我们的代码里我们用了一个宏XX_SQL_MAX_LEN来定义了一条语句内容的最长值,而XX_SQL_MAX_LEN

在我们的系统中被赋予512这个整数值。这样我们很容易得到如下的定义:
char sql1[XX_SQL_MAX_LEN]; //遗憾的是结尾''也被我们算进了XX_SQL_MAX_LEN中了。
char sql2[XX_SQL_MAX_LEN];

一般sql语句的前端的select字段list是固定的,其长度也就是固定的;但是后面的where语句中的条件则是由参数指定。在我们的系统里这个条件很简单,就只有一个索引值(index)。

让我们意想不到的是在我们要读取的那个数据表的操作语句中,select字段list的长度就超出我们预想,达到了508个字节,要知道目前我们还不知道这一事实。我们按照一般数据库操作流程:先读count,再读数据。结果呢?我们读出了若干条数据,然后就在某一条数据上出现了上述错误。这个数据没有什么特殊的,除了其index的值的位数变成了四位之外。

警觉的我们发现:系统在读取index是三位整数的记录时都是没有问题的,偏偏到index是四位整数时才会有问题,而问题偏偏又出在执行sql1上。直觉告诉我们又是内存问题。

分析如下:
sql1和sql2在栈上紧挨着,是否生成sql2的时候污染了sql1的数据呢?我们试图打印出sql1的值,结果发现printf的输出居然是空,这更坚定了内存遭受破坏的想法。遂掰手指头数sql2的长度,发现其长度居然恰好512字节。我们拿一个栈图来说明问题:


栈图

正如上图中所示:sql2的数据多的沿着数据的延伸方向一直越过了边界来到了sql1的境地,在sql1第一个字节上留下一个''就结束了。这就是为什么sql1打印为空的原因。同样由于传给OCIStmtPrepare的参数stmt_len的值为strlen(sql1),导致stmt_len变为0,也就恰好与ORA-24373这个错误码的说明一致了。这简直太巧了,太不可思议了。不多不少恰好512。

程序员的想当然造就了这一切,就好比千年虫一样,给你我带来麻烦。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats