标签 Unix 下的文章

都是病毒惹得祸

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

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

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

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

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

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

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

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

也谈’SIGBUS和SIGSEGV’

SIGBUS和SIGSEGV也许是我们在平时遇到的次数最多的两个内存错误信号。内存问题一直是最令我们头疼的事情,弄清楚两个信号的发生缘由对我们很好的理解程序的运行是大有裨益的。

我们来看两段程序:
//testsigsegv.c
int main() {
        char *pc = (char*)0×00001111;
        *pc = 17;
}

//testsigbus.c
int main() {
        int *pi = (int*)0×00001111;
        *pi = 17;
}

上面的代码那么的相似,我们也同样用gcc编译(加上-g选项,便于gdb调试;平台Solaris Sparc),执行结果也都是dump core。但通过GDB对core进行观察,你会发现细微的不同。第一个例子出的core原因是:Program terminated with signal 11, Segmentation fault. 而第二个例子的core则提示:Program terminated with signal 10, Bus error. 两者有什么不同呢?这两段代码的共同点都是将一个非法地址赋值给指针变量,然后试图写数据到这个地址。

如果要说清楚这个问题,我们就要结合汇编码和一些计算机的体系结构的知识来共同分析了。

先来看testsigsegv.c的汇编码:
… …
main:
        !#PROLOGUE# 0
        save    %sp, -120, %sp
        !#PROLOGUE# 1
        sethi   %hi(4096), %i0
        or      %i0, 273, %i0
        st      %i0, [%fp-20]
        ld      [%fp-20], %i1
        mov     17, %i0
        stb     %i0, [%i1]
        nop
        ret
        restore
… …

我们关注的是这句:stb     %i0, [%i1]
从计算机底层的执行角度来说,过程是如何的呢?%i0寄存器里存储的是立即数17,我们要将之存储到寄存器%i1的值指向的内存地址。这一过程对于CPU来说其指挥执行的正常过程是:将寄存器%i0中的值送上数据总线,将寄存器%i1的值送到地址总线,然后使能控制总线上的写信号完成这一向内存写1 byte数据的过程。

我们再看testsigbus.c的汇编码:
… …
main:
        !#PROLOGUE# 0
        save    %sp, -120, %sp
        !#PROLOGUE# 1
        sethi   %hi(4096), %i0
        or      %i0, 273, %i0
        st      %i0, [%fp-20]
        ld      [%fp-20], %i1
        mov     17, %i0
        st      %i0, [%i1]
        nop
        ret
        restore
… …

同样最后一句:st      %i0, [%i1],CPU执行的过程与testsigsegv.c中的一致(只是要存储数据长度是4字节),那为什么产生错误的原因不同呢?一个是SIGSEGV,而另一个是SIGBUS。这里涉及到的就是对内存地址的校验的问题了,包括对内存地址是否对齐的校验以及该内存地址是否合法的校验。

我们假设如果首先进行的内存地址是否合法的校验(是否归属于用户进程的地址空间),那么我们回顾一下,这两个程序中的地址0×00001111显然都不合法,按照这种流程,两个程序都应该是SIGSEGV导致的core才对,但是事实并非如此。那难道是先校验内存地址的对齐?我们再看这种思路是否合理?

testsigsegv.c中,0×00001111这个地址值被赋给了char *pc;也就是告诉CPU通过这个地址我们要存取一个字节的值,对于一个字节长度的数据,无所谓对齐,所以该地址通过对齐校验;并被放到地址总线上了。而在testsigbus.c里,0×00001111这个地址值被赋给了int *pi;也就是告诉CPU通过这个地址我们要存取一个起码4个字节的值,那么对于长度4个字节的对象,其存放地址起码要被4整除才可以,而0×00001111这个值显然不能满足要求,也就不能通过内存对齐的校验。也就是说SIGBUS这个信号在地址被放到地址总线之后被检查出来的不符合对齐的错误;而SIGSEGV则是在地址已经放到地址总线上后,由后续流程中的某个设施检查出来的内存违法访问错误。

一般我们平时遇到SIGBUS时总是因为地址未对齐导致的,而SIGSEGV则是由于内存地址不合法造成的。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系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