面向祖传代码 Debug,我挽回了一位准备跑路的程序员

简介: 交流群的风格突然骤变,没有了往日的灌水扯淡,居然聊起了技术。看了大家的全部的聊天记录,发现问题并没解决。群里难得这么多人聊技术,抱着问答不断,必有回响的原则,主动勾搭一起看看是什么问题。大概了解其问题是这样,apache 子进程一直异常退出。

交流群的风格突然骤变,没有了往日的灌水扯淡,居然聊起了技术。
1623650919857100.jpg
1623650924550111.jpg

看了大家的全部的聊天记录,发现问题并没解决。群里难得这么多人聊技术,抱着问答不断,必有回响的原则,主动勾搭一起看看是什么问题。

大概了解其问题是这样,apache 子进程一直异常退出

$ sudo tail -f /var/log/httpd/error_log
[Sun Jun 13 19:32:25.660349 2021] [core:notice] [pid 23340] AH00052: child pid 20605 exit signal Bus error (7)
[Sun Jun 13 19:32:25.660413 2021] [core:notice] [pid 23340] AH00052: child pid 20606 exit signal Bus error (7)
[Sun Jun 13 19:35:39.862368 2021] [core:notice] [pid 23340] AH00052: child pid 21332 exit signal Bus error (7)
[Sun Jun 13 19:35:50.872363 2021] [core:notice] [pid 23340] AH00052: child pid 21369 exit signal Bus error (7)
[Sun Jun 13 19:39:43.079650 2021] [core:notice] [pid 23340] AH00052: child pid 18595 exit signal Bus error (7)
[Sun Jun 13 19:42:08.210353 2021] [core:notice] [pid 23340] AH00052: child pid 21348 exit signal Bus error (7)
[Sun Jun 13 19:42:16.221076 2021] [core:notice] [pid 23340] AH00052: child pid 21331 exit signal Bus error (7)
[Sun Jun 13 19:42:16.221177 2021] [core:notice] [pid 23340] AH00052: child pid 23156 exit signal Bus error (7)
[Sun Jun 13 19:44:11.329344 2021] [core:notice] [pid 23340] AH00052: child pid 27824 exit signal Bus error (7)

现场复现

了解到其架构是 apache + php ,群友也是临时接受的祖传代码,项目也不是很清楚。可以使用strace来看看,背后到底是什么问题

$ ps -ef|grep apache

首先看到主进程 id 是 23340,这里需要跟着其子进程,所以我执行了如下命令

$ sudo strace -o strace.log -s 1024 $(pidof "/usr/sbin/httpd" -o 23340|sed 's/\([0-9]*\)/-p \1/g')
strace: Process 26212 attached
strace: Process 26211 attached
strace: Process 26191 attached
strace: Process 25940 attached
...

参数大概解释下

code 释义
-o strace.log 输出 strace 日志到 strace.log
-s 1024 当系统调用的某个参数是字符串时,最多输出指定长度的内容,默认是32个字节
pidof "/usr/sbin/httpd" -o 23340 排除主进程 23340 之外的 httpd 子进程

然后新开一个窗口监控日志

$ sudo tail -f strace.log |grep SIGBUS -B 500
28741 open("/xxxx/www/data/cache/xxx.php", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 15
28741 write(15, "<?php defined('DYMall') or exit('Access Invalid!'); return array ( ) ?>", 71) = 71
28741 close(15)
...
28741 open("/xxxx/www/data/cache/xxx.php", O_RDONLY) = 15
28741 fstat(15, {st_mode=S_IFREG|0644, st_size=71, ...}) = 0
28741 fstat(15, {st_mode=S_IFREG|0644, st_size=71, ...}) = 0
28741 fstat(15, {st_mode=S_IFREG|0644, st_size=71, ...}) = 0
28741 mmap(NULL, 71, PROT_READ, MAP_SHARED, 15, 0) = 0x7f584aee8000
28741 --- SIGBUS {si_signo=SIGBUS, si_code=BUS_ADRERR, si_addr=0x7f584aee8000} ---
28741 chdir("/etc/httpd")               = 0
28741 rt_sigaction(SIGBUS, {SIG_DFL, [], SA_RESTORER|SA_INTERRUPT, 0x7f58499bc5e0}, {SIG_DFL, [], SA_RESTORER|SA_RESETHAND, 0x7f58499bc5e0}, 8) = 0
28741 kill(28741, SIGBUS)               = 0
28741 rt_sigreturn({mask=[]})           = 140017190993928
28741 --- SIGBUS {si_signo=SIGBUS, si_code=SI_USER, si_pid=28741, si_uid=48} ---
28741 +++ killed by SIGBUS +++
SIGBUS 在用户态最为常见的场景,也最容易触发,通常来说根本原因都是进程 mmap 了一个文件后,另外的进程把这个文件截断了,导致 mmap 出来的某些内存页超出文件的实际大小,访问那些超出的内存页就会触发 SIGBUS

验证文件大小变化

监控文件的大小来验证文件是否一直在变化

$ while :; do cat /xxxx/www/data/cache/xxx.php|wc -L ;done
$ while :; do cat /xxxx/www/data/cache/xxx.php|wc -L ;done|grep -v 71

发现文件确实在少数情况下会变为 0 个字节,根据文件的路径,初步估计应该是缓存文件被重写了

确认文件写操作来源

$ sudo yum -y install audit auditd-libs

监控topic_goodsclass.php文件的写入操作

$ sudo auditctl -w /xxxx/www/data/cache/xxx.php -p w

结果类似于

time->Mon Jun 14 21:21:39 2021
type=PROCTITLE msg=audit(1623676899.778:1883303): proctitle=2F7573722F7362696E2F6874747064002D44464F524547524F554E44
type=PATH msg=audit(1623676899.778:1883303): item=1 name="/xxxx/www/data/cache/xxx.php" inode=30151674 dev=fd:11 mode=0100644 ouid=48 ogid=48 rdev=00:00 objtype=NORMAL
type=PATH msg=audit(1623676899.778:1883303): item=0 name="/xxxx/www/data/cache/" inode=30151667 dev=fd:11 mode=040755 ouid=48 ogid=48 rdev=00:00 objtype=PARENT
type=CWD msg=audit(1623676899.778:1883303):  cwd="/xxxx/www"
type=SYSCALL msg=audit(1623676899.778:1883303): arch=c000003e syscall=2 success=yes exit=16 a0=55b452b93078 a1=241 a2=1b6 a3=2a items=2 ppid=31318 pid=18269 auid=4294967295 uid=48 gid=48 euid=48 suid=48 fsuid=48 egid=48 sgid=48 fsgid=48 tty=(none) ses=4294967295 comm="httpd" exe="/usr/sbin/httpd" key=(null)

收集一段时间,做下统计

$ sudo ausearch -f /xxxx/www/data/cache/xxx.php |grep "exe="|awk '{print $12,$26}'|sort|uniq -c
  44200 ppid=31318 exe="/usr/sbin/httpd"

发现都是来自于/usr/sbin/httpd,并没有CLI模式的写入(担心有定时任务来生成缓存),清理掉监控

sudo auditctl -D

多进程文件读写冲突的解决方案

需要注意,在对文件进行fopen($filename, "w+")的时候,就已经将文件清空了,所以加锁需要在fopen目标文件之前,所以我的方式是增加一个文件一一对应的锁文件。如下:
aa.php

function addFileLock($filename, $lock)
{
    $fp = fopen($filename . ".lock", "w+");
    flock($fp, $lock);
    return $fp;
}

function releaseLock($fp)
{
    flock($fp, LOCK_UN);
}

$filename = "cc.log";


// mock 数据
file_put_contents($filename, "100");

$fp = fopen($filename, "r");

if ($lockfp = addFileLock($filename, LOCK_SH)) {

    $n = 10;
    while ($n > 0) {
        sleep(1);
        printf("%d %d\n", time(), --$n);
    }

    echo fgets($fp);

    releaseLock($lockfp);
}

fclose($fp);

bb.php

function setFileLock($filename, $lock)
{
    $fp = fopen($filename . ".lock", "w+");
    flock($fp, $lock);
    return $fp;
}

function releaseFileLock($fp)
{
    flock($fp, LOCK_UN);
    fclose($fp);
}

$filename = "cc.log";

if ($lockfp = setFileLock($filename, LOCK_EX)) {
    $fp = fopen($filename, "w+");
    echo time();
    fwrite($fp, 200);
    releaseFileLock($lockfp);
}

fclose($fp);

所以先执行 php aa.php 再执行 php bb.php,就能看到效果。

实际代码定位

根据系统调用里面的字符串关键字,搜到了相关的缓存操作代码

28741 write(15, "<?php defined('DYMall') or exit('Access Invalid!'); return array ( ) ?>", 71) = 71

改完之后发现线上还是有问题,本来我已经怀疑人生了,还是通过系统调用发现读取的时候并没有走我增加的共享锁
WechatIMG536.jpg
原来缓存的读取是走的别的逻辑,从上面的截图可以看到缓存的写入已经走了修改之后的代码(加锁逻辑),然后根据个人经验加搜索该缓存写入方法,按图索骥找到了另外一个缓存文件的读取逻辑,然后再次修改,线上问题没有再复现。

问题解决,跑路哥给我发来红包,我心满意足的接受了。

IMG_9532.jpg

总结

  1. 首先通过strace复现SIGBUS的场景
  2. 预计是文件在多进程模型下,并发读写导致的问题
  3. 通过auditctl监控统计文件的写操作来源均为httpd没有其他进程来修改
  4. 通过读加共享锁、写加排它锁,线上还是有问题
  5. 再次根据strace 发现读的地方另有他处,根据经验,补上,问题修复

不足的地方:最后定位过于依靠个人经验,不够系统化。

小想法:可以弄一个扩展使用 zend_set_user_opcode_handler 来监控自定义函数和方法和系统方法和函数调用链,然后在每个请求初始化阶段写入日志,比如

write(fd, "{unique code}", xxx);
write(fd, "request uri", xxx);

然后的方法调用在 zend_set_user_opcode_handler里面打印,这样就能方便排查祖传代码的一些业务逻辑问题了。

如果有其他更快更简单的方案,大家留言告诉我。

“程序员跑路笔记” 我的公众号,期待你的关注,一起避免跑路

qrcode_for_gh_6c1e2273268c_258.jpg

目录
相关文章
|
监控 物联网 数据安全/隐私保护
以太网供电(POE):技术详解与应用探索
【4月更文挑战第22天】
1523 2
|
安全 数据处理 数据安全/隐私保护
企业出海数据合规:何为数据脱敏
数据脱敏并非简单技术手段,其涵盖法律与技术双重维度。法律上,脱敏是保护个人隐私的一种效果,技术上则是采用不可逆或难以还原的方法,降低数据泄露风险。GDPR下,个人身份、账户和健康信息等应脱敏处理,程度可根据数据敏感性确定。脱敏常见方法包括随机化、掩码、加密等,旨在保护数据安全与隐私。
1641 0
|
7月前
|
设计模式 缓存 监控
如何在 Spring 项目中优雅地使用设计模式
本文深入探讨在Spring项目中如何优雅应用设计模式,结合依赖注入与IoC特性,通过工厂、策略、装饰者等模式提升代码可维护性与扩展性,助力构建高效、灵活的Java应用。
395 5
|
3月前
|
人工智能 文字识别 内存技术
阿里云Tokens如何收费?免费千万Tokens领取及亿万AI大模型扶持计划申请入口
阿里云Tokens按输入/输出分别计费,价格因模型而异(如Qwen-Turbo:0.0003/0.0006元/千Tokens)。新用户开通百炼平台可免费领7000万Tokens(各模型享100万额度),企业认证还可申领万亿Tokens扶持及2000元券,免费Tokens申请链接:https://t.aliyun.com/U/fPVHqY
577 6
|
人工智能 自然语言处理 API
大模型编程(3)让 AI 帮我调接口
这是大模型编程系列第三篇,分享学习某云大模型工程师ACA认证免费课程的笔记。本文通过订机票和查天气的例子,介绍了如何利用大模型API实现函数调用,解决实际业务需求。课程内容详实,推荐感兴趣的朋友点击底部链接查看原文,完全免费。通过这种方式,AI可以主动调用接口并返回结果,极大简化了开发流程。欢迎在评论区交流实现思路。
1555 1
|
存储 人工智能 并行计算
【AI系统】算子开发编程语言 Ascend C
本文详细介绍了昇腾算子开发编程语言 Ascend C,旨在帮助开发者高效完成算子开发与模型调优。Ascend C 原生支持 C/C++标准,通过多层接口抽象、自动并行计算等技术,简化开发流程,提高开发效率。文章还探讨了并行计算的基本原理及大模型并行加速策略,结合 Ascend C 的 SPMD 编程模型和流水线编程范式,为读者提供了深入理解并行计算和 AI 开发的重要工具和方法。
791 2
|
运维 安全 持续交付
Dockerfile中小型企业实战指南
本文档旨在为中小企业提供一份 实用、易懂 的 Dockerfile 实践指南。我们深知中小企业在技术标准化、快速迭代和资源有效利用方面的需求,因此本教程将涵盖从基础概念到进阶实战的多层次 Docker 镜像构建方法,重点关注如何在有限资源下,通过规范的 Dockerfile 来提升开发、测试和部署效率。无论您是初学者还是有一定经验的开发者、运维人员,都能从中获益,快速掌握不同场景下的镜像制作和优化技巧,最终构建出 安全、高效、轻量且易于维护 的容器镜像。
411 5
HTTPS 证书自动化运维:https证书管理系统之自动化签发
通过访问【https://www.lingyanspace.com】注册账户,进入证书服务菜单并新增证书。填写域名(单域名、多域名或泛域名),创建订单后添加云解析DNS记录进行质检。确认完成后可下载证书,并支持后续查看、更新和定时更新功能。证书过期前15天自动更新,需配置邮箱接收通知。
HTTPS 证书自动化运维:https证书管理系统之自动化签发
|
移动开发 索引
技术笔记:Word域功能详细介绍(一)
技术笔记:Word域功能详细介绍(一)