当前位置: 首页 > 后端技术 > PHP

面对祖传代码Debug,救了一个正要逃跑的程序员

时间:2023-03-30 05:20:28 PHP

交流群的画风一下子就变了,老废话不说了,居然还聊技术。看了大家所有的聊天记录,发现问题并没有解决。难得有这么多人在群里聊技术。以有问有答,必有回应为原则,主动勾搭看看是什么问题。大致了解了其他问题是这样的,apache子进程一直异常退出$sudotail-f/var/log/httpd/error_log[SunJun1319:32:25.6603492021][core:notice][pid23340]AH00052:childpid20605退出信号总线错误(7)[SunJun1319:32:25.6604132021][core:notice][pid23340]AH00052:childpid20606exitsignalBuserror(7)[SunJun1319:35:39.8623682021][core:notice][pid23340]AH00052:childpid21332exitsignalBuserror(7)[SunJun1319:35:50.8723632021][core:notice][pid23340]AH00052:childpid21369exitsignalBus错误(7)[SunJun1319:39:43.0796502021][core:notice][pid23340]AH00052:childpid18595exitsignalBuserror(7)[SunJun1319:42:08.2103532021][core:notice][pid23340]AH00052:childpid21348exitsignalBuserror(7)[SunJun1319:42:16.2210762021][core:notice][pid23340]AH00052:childpid21331exitsignalBuserror(7)[SunJun1319:42:16.2211772021][core:notice][pid23340]AH00052:childpid23156exitsignalBuserror(7)[SunJun1319:44:11.3293442021][core:notice][pid23340]AH00052:childpid27824exitsignalBuserror(7)现场重现得知其架构是apache+php,群友也暂时收下祖传代码,项目也不是很清楚可以用strace看看后面是什么问题$ps-ef|grepapache首先看到主进程id是23340,这里需要跟踪它的子进程,所以我执行了如下命令$sudostrace-ostrace。log-s1024$(pidof"/usr/sbin/httpd"-o23340|sed's/\([0-9]*\)/-p\1/g')strace:进程26212attachedstrace:进程26211attachedstrace:Process26191attachedstrace:Process25940attached...参数在代码解释中大致解释-ostrace.log输出strace日志到strace.log-s1024当系统调用的参数为字符串时,最多输出指定长度的内容,默认为32字节。pidof"/usr/sbin/httpd"-o23340排除除主进程23340以外的httpd子进程,然后新开窗口监控日志$sudotail-fstrace.log|grepSIGBUS-B50028741open("/xxxx/www/data/cache/xxx.php",O_WRONLY|O_CREAT|O_TRUNC,0666)=1528741write(15,"",71)=7128741关闭(15)...28741打开("/xxxx/www/data/cache/xxx.php",O_RDONLY)=1528741fstat(15,{st_mode=S_IFREG|0644,st_size=71,...})=028741fstat(15,{st_mode=S_IFREG|0644,st_size=71,...})=028741fstat(15,{st_mode=S_IFREG|0644,st_size=71,...})=028741mmap(NULL,71,PROT_READ,MAP_SHARED,15,0)=0x7f584aee800028741---SIGBUS{si_signo=SIGBUS,si_code=BUS_ADRERR,si_addr=0x7f584aee8000}US---28741chdir("/etc/httpigd41rt(SI_GBsd")=0287,{SIG_DFL,[],SA_RESTORER|SA_INTERRUPT,0x7f58499bc5e0},{SIG_DFL,[],SA_RESTORER|SA_RESETHAND,0x7f58499bc5e0},8)=028741kill(28741,SIGBUS)=028741rt_sigre=[turn]({)=mas14001719099392828741---SIGBUS{si_signo=SIGBUS,si_code=SI_USER,si_pid=28741,si_uid=48}---28741+++killedbySIGBUS+++SIGBUS是用户态最常见的场景,也是最容易触发。根本原因是一个进程mmap一个文件后,另一个进程截断了这个文件,导致一些被mmap的内存页超过了文件的实际大小。访问那些多余的内存页会触发SIGBUS来验证文件大小变化监控文件的大小来验证文件是否一直在变化$while:;做cat/xxxx/www/data/cache/xxx.php|wc-L;done$while:;docat/xxxx/www/data/cache/xxx.php|wc-L;done|grep-v71发现文件在少数情况下确实会变成0字节。根据文件的路径,初步估计应该是缓存文件被改写了。确认文件写操作的来源$sudoyum-yinstallauditauditd-libs监控topic_goodsclass.php文件的写操作$sudoauditctl-w/xxxx/www/data/cache/xxx.php-pw结果类似于time->MonJun1421:21:392021type=PROCTITLEmsg=audit(1623676899.778:1883303):proctitle=2F7573722F7362696E2F6874747064002D44464F524547524F554E44type=PATHmsg=audit(1623676899.778:1883303):item=1name="/xxxx/www/data/cache/xxx.php"inode=30151674dev=fd:11mode=0100644ouid=48ogid=48rdev=00:00objtype=NORMALtype=PATHmsg=audit(1623676899.778:1883303):item=0name="/xxxx/www/data/cache/"inode=30151667dev=fd:11mode=040755ouid=48ogid=48rdev=00:00objtype=PARENTtype=CWDmsg=audit(1623676899.778:1883303):cwd="/xxxx/www"type=SYSCALLmsg=audit(1623676899.778:1883303):arch=c000003esyscall=2success=yesexit=16a0=55b452b93078a1=26ams=2pid=31318=18269auid=4294967295uid=48gid=48euid=48suid=48fsuid=48egid=48sgid=48fsgid=48tty=(none)ses=4294967295comm="httpd"exe="/usr/sbin/httpd"key=(null)收集一段时间并做统计$sudoausearch-f/xxxx/www/data/cache/xxx.php|grep"exe="|awk'{print$12,$26}'|sort|uniq-c44200ppid=31318exe="/usr/sbin/httpd"发现都是从/usr/sbin/httpd来的,CLI模式下没有写入(担心定时任务生成缓存),cleanup监控sudoauditctl-D多进程文件读写冲突的解决方法需要注意的是,当文件fopen($filename,"w+")时,文件已经被清除了,所以需要加锁在fopen目标文件前加锁,所以我的做法是加一个文件对应一个文件的锁如下:aa.phpfunctionaddFileLock($filename,$lock){$fp=fopen($filename.".lock","w+");羊群($fp,$lock);return$fp;}functionreleaseLock($fp){flock($fp,LOCK_UN);}$filename="cc.log";//mock数据file_put_contents($filename,"100");$fp=fopen($文件名,"r");if($lockfp=addFileLock($filename,LOCK_SH)){$n=10;while($n>0){睡眠(1);printf("%d%d\n",时间(),--$n);}echofgets($fp);releaseLock($lockfp);}fclose($fp);bb.phpfunctionsetFileLock($filename,$lock){$fp=fopen($filename.".lock","w+");羊群($fp,$lock);返回$fp;}functionreleaseFileLock($fp){flock($fp,LOCK_UN);fclose($fp);}$filename="cc.log";if($lockfp=setFileLock($filename,LOCK_EX)){$fp=fopen($filename,"w+");回声时间();fwrite($fp,200);releaseFileLock($lockfp);}fclose($fp);所以先执行phpaa.php再执行phpbb.php,就能看到效果。实际代码位置根据系统调用中的字符串关键字,找到相关的缓存操作代码28741。write(15,"",71)=修改71后,发现网上还是有问题。我已经怀疑我的人生了。通过系统调用发现我加的共享锁在读的时候并没有用到。从上面的截图我们可以看出缓存写入已经经过了修改代码(加锁逻辑),然后根据个人经验搜索缓存写入方式,根据图片找出另一个缓存文件的读取逻辑.,然后再次修改,线上问题没有再出现。问题解决了,跑路哥给我发了红包,我满意的收下了。总结首先,通过strace复现SIGBUS的场景,预计是多进程模型下文件并发读写导致的问题。通过auditctl监控统计该文件的写操作来源为httpd。被锁了,上线还是有问题。根据strace,发现阅读的地方在别处。根据经验,我会弥补和修复问题:最终定位过于依赖个人经验,不够系统。小idea:可以做一个扩展使用zend_set_user_opcode_handler来监控自定义函数和方法以及系统方法和函数调用链,然后在每个请求初始化阶段写日志,比如write(fd,"{uniquecode}",xxx);write(fd,"请求uri",xxx);然后在zend_set_user_opcode_handler中打印方法调用,方便排查祖传代码的一些业务逻辑问题。如果还有其他更快更简单的解决方案,请留言告诉我。