strace帮助你调试PHP代码

strace帮助你调试PHP代码上个礼拜,一个Crontab后台脚本(PHP代码)运行遇到一些问题,最后借助 strace linux 命令行工具快速解决了,虽然问题产生和解决很简单,但 strace 工具却值得每个程序员去学习。这个后台脚本在开发环境没有遇到问题,但在线上环境却出现了问题,开发环境和线上环…

上个礼拜,一个Crontab后台脚本(PHP代码)运行遇到一些问题,最后借助 strace linux 命令行工具快速解决了,虽然问题产生和解决很简单,但 strace 工具却值得每个程序员去学习。

这个后台脚本在开发环境没有遇到问题,但在线上环境却出现了问题,开发环境和线上环境在代码层没有太多的差别(也是事后才发现有差异),仅仅是开发环境使用的是测试数据库等资源。

线上环境脚本运行的结果就是某个文件没有成功记录数据,按照常规的解决思路,就是在PHP代码中增加调试函数(比如 var_dump 函数)一步步调试,针对这个脚本来说,使用这种方法可能会有以下一些问题:

  • 开发环境没有问题,为重现问题,必须在线上环境进行调试,如果增加调试代码,可能会引发一些问题,比如在线上数据库额外插入了一些数据。

  • 该脚本运行比较缓慢,如果不停的修改代码然后调试,最后花费的时间可能就比较长。

那有没有快速的调试手段呢?这个脚本预期结果就是在文件中写入日志信息,也就是脚本最终会调用系统函数写入文件,那么是遇到文件权限问题?脚本中断没有运行到写入文件步骤?有没有类似于 tcmdump 这样的工具,能够重现所有的系统处理呢?非常棒的是,strace 就是干这活的,它能够记录所有的系统调用和信号处理。

由于脚本没有成功 fwrite 写入数据,我将该脚本的关键代码抽取出来,代码如下:

<?php
$file  = "/var/log/data.log";
$fp = fopen($file, "a");
if ($fp) {
    echo "start\n";
    foreach ($log as $v) {
       fwrite($fp, $v."\r\n");
    }
}
fclose($fp);

然后运行下列的命令,记录所有的系统调用:

$ strace -o debug.log php test.php

查看关键输出:

lstat("/var/log/data.log", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
lstat("/var/log", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
lstat("/var", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
open("/var/log/data.log", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
lseek(3, 0, SEEK_CUR)  = 0
lseek(3, 0, SEEK_CUR)  = 0
write(1, "start\n", 6) = 6
close(3) = 0
close(2) = 0
close(1) = 0
munmap(0x7f7aa7dfd000, 4096) = 0
close(0) = 0

通过输出可以看出,脚本根本没有运行 fwrite($fp, $v."\r\n"); 这段代码,其他一切运行正常,代码也正常结束了。

问题已经很明显了,是 $log 变量在线上环境为空,顺藤摸瓜,仔细检查了 $log 变量的处理,原来是存在潜在的 Bug(具体就不描述了,属于低级错误)。修改代码后,再一次运行 strace,输出结果如下:

open("/var/log/data.log", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
lseek(3, 0, SEEK_CUR)  = 0
lseek(3, 0, SEEK_CUR)  = 0
write(1, "start\n", 6) = 6
write(3, "testdata\r\n", 10) = 10
close(3)  = 0
close(2)  = 0
close(1)  = 0
munmap(0x7f8a034cd000, 4096)  = 0

脚本最终成功写入数据到文件中(write(3, "testdata\r\n", 10) = 10),问题解决,其实这个问题的产生和原因,最终是代码不严谨造成的,该例也并不能说明 strace 工具的牛逼,因为其他的调试手段同样也能解决,但不可否认的是 strace 工具有非常广泛的使用场景。

接下来我们系统理解下 strace 工具,关于该工具的使用参数和输出可以参考 man strace,在 man 帮助中,对 strace 的定义如下。

strace – trace system calls and signals

strace  is  a useful diagnostic, instructional, and debugging tool

以 PHP 代码来说,代码的大部分逻辑都会产生系统调用,比如写入本地文件(write),连接外部数据库(connect),即使你完全不知道代码的逻辑,通过 strace 工具也能够知晓 PHP 代码做了哪些事情,这是不是很奇妙?

接下去简单举几个例子,让我们掌握 strace 的使用。

1:例子一

$ strace -Tt php test.php

-T 参数表示每一个系统调用花费的时间,-t 是输出每个系统调用发生的时间。

test.php 脚本是想连接 google.com,关键输出如下:

17:00:21 connect(3, {sa_family=AF_INET, sin_port=htons(443), sin_addr=inet_addr("173.252.73.48")}, 16) = -1 EINPROGRESS (Operation now in progress) <0.000057>
17:00:21 poll([{fd=3, events=POLLIN|POLLOUT|POLLERR|POLLHUP}], 1, 60000) = 0 (Timeout) <60.055735>

通过输出可见,最终 poll 尝试了 60 秒,调用超时了,这对于调试网络理解非常有用。每一行的输出分别表示、系统调用开始时间、系统调用命令、系统调用参数、系统调用返回值、系统调用处理时间。

2:例子二

# 输出 nginx 工作进程 PID 号
$ ps -uax | grep "nginx: worker process" | grep -v "grep" | awk '{print $2}' 
29785 

$ strace -p  29785 -F

上面的例子,strace 观察 nginx 工作进程的运行,从而可以了解其内部处理连接的原理。

其中 -p 参数表示跟踪进程 PID 号,-F 表示过程该进程调用的子进程(比如 PHP 执行 exec 调用),这是非常重要的一个参数。

输出如下:

Process 29785 attached
epoll_wait(9, {{EPOLLIN, {u32=3975708920, u64=140539600601336}}}, 512, -1) = 1
accept4(7, {sa_family=AF_INET, sin_port=htons(50787), sin_addr=inet_addr("218.30.113.40")}, [16], SOCK_NONBLOCK) = 3
epoll_ctl(9, EPOLL_CTL_ADD, 3, {EPOLLIN|EPOLLRDHUP|EPOLLET, {u32=3975710081, u64=140539600602497}}) = 0
epoll_wait(9, {{EPOLLIN, {u32=3975710081, u64=140539600602497}}}, 512, 60000) = 1
recvfrom(3, "\26", 1, MSG_PEEK, NULL, NULL) = 1
setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0
read(3, "\26\3\1\2\24\1\0\2\20\3\3\361\365r\343(\362p\320RV9\1\316S\31jCQ\211\22\264"..., 16709) = 537

可见 nginx 接收到了一个来自于 218.30.113.40:50787 请求,整个过程对于理解网络编程非常有用。

3:例子三

$ strace -c -o out.log php test.php

-c 参数能够汇总系统调用的报告,比如某个系统调用的次数、失败数等等,-o 参数可以将 strace 输出保存到 out.log 文件中。

类似的输出结果如下:

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 21.57    0.000107           1       169           mmap
 18.75    0.000093           1        88           mprotect
 15.12    0.000075           1        81         3 open
 11.29    0.000056           1        80           rt_sigaction
  7.26    0.000036           2        20        19 access
  6.05    0.000030           1        47           read

如果一个脚本运行非常缓慢,那么这个参数大概能了解那个系统缓慢,从而找出问题。

最后介绍一个非常有用的调试参数,可以使用 -e 控制正则表达式,比如输出所有与 write 有关的调用。

$ strace -e write php test.php

该命令输出如下:

write(1, "start\n", 6start
)                  = 6
write(3, "testdata\r\n", 10)            = 10
+++ exited with 0 +++

使用这工具并不能,难的是如何具体分析问题,让工具帮助你,这篇文章也仅仅是提出一个思路,最重要的是进行实践,获取经验。

今天的文章strace帮助你调试PHP代码分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/17051.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注