strace 跟踪多线程程序不能打印系统调用的问题

问题描述

在分析 netlink: 8 bytes leftover after parsing attributes in process server这个内核异常打印日志的时候,最先想到可以通过 strace 来跟踪进程,看看是在执行哪个系统调用的时候打印的告警,理论上能够行的通。

实际测试发现,strace -p 跟踪到 server 程序后,没有追踪到新的系统调用,等了几分钟也没有任何的打印。使用 strace 跟踪其它进程是正常的,说明 strace 命令本身可能没有问题,问题可能出在我们的 server 中。

一开始没有想明白这里的机关,就转向去研究 netlink 的执行过程了。

对这个问题的思考

最近定位一些问题时,我对这个问题有了新的认识,我发现对于多线程的程序,每一个线程都有一个 pid,且在 /proc 下都有一个目录,但是 ps 的时候只能够看到一个 pid,这个 pid 实际就是进程的 pid,也是领头线程的 pid。

有这样的信息,我觉得可能 strace -p 跟踪 server 没有新的系统调用记录是因为 strace -p 跟踪的领头线程一直阻塞,没有执行系统调用的结果。

strace 命令 manual 中的相关信息

man strace 浏览发现了如下相关内容:

  -f          Trace child processes as they are created by currently traced processes as a result of the fork(2), vfork(2) and clone(2) system calls.  Note that -p
                   PID -f will attach all threads of process PID if it is multi-threaded, not only thread with thread_id = PID.

上述内容说明,使用了 -p PID -f 参数,如果进程是多线程程序,那么 strace 将会跟踪到 PID 指向程序的所有线程,而不是 thread id 与 PID 相等的线程。

编写程序测试验证

下面这个代码中子线程在一段时间内保持运行,同时领头线程通过 pthread_join 等待子线程死亡。

源码如下:

#include <pthread.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>

#define handle_error_en(en, msg)                                        \
    do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)

static void *
thread_start(void *arg)
{
    struct thread_info *tinfo = arg;
    int count = 0;

    while(count < 100000) {
        printf("thread running %d times\n", count);
        usleep(10000);
        count++;
    }

    return NULL;
}

int
main(int argc, char *argv[])
{
    pthread_t tid;
    int ret;
    void *res;

    ret = pthread_create(&tid, NULL,
                         &thread_start, NULL);
    if (ret != 0)
        handle_error_en(ret, "pthread_create");

    ret = pthread_join(tid, &res);
    if (ret != 0)
        handle_error_en(ret, "pthread_join");

    free(res);  
    exit(EXIT_SUCCESS);
}

将上述程序保存为 thread.c,然后执行如下命令进行编译:

gcc thread.c -o thread -lpthread

编译成功后进行下面的测试过程:

执行 strace -p PID

[longyu@debian-10:23:10:50] The_C_Language $ ./thread > /dev/null &
[1] 12414
[longyu@debian-10:23:11:00] The_C_Language $ sudo strace -p 12414
strace: Process 12414 attached
futex(0x7f6e0fd6f9d0, FUTEX_WAIT, 12417, NULL^Cstrace: Process 12414 detached
 <detached ...>

这里我首先后台执行 thread 程序,然后使用 strace -p 跟踪进程 pid,发现一直没有新的系统调用打印出来。

执行 strace -p PID -f

执行 strace -p PID -f 能够看到子线程的系统调用能够监控到,测试过程记录如下;

[longyu@debian-10:23:11:04] The_C_Language $ sudo strace -p 12414 -f 
strace: Process 12414 attached with 2 threads
[pid 12414] futex(0x7f6e0fd6f9d0, FUTEX_WAIT, 12417, NULL <unfinished ...>
[pid 12417] restart_syscall(<... resuming interrupted nanosleep ...>) = 0
[pid 12417] nanosleep({tv_sec=0, tv_nsec=10000000}, NULL) = 0
[pid 12417] nanosleep({tv_sec=0, tv_nsec=10000000}, NULL) = 0
[pid 12417] nanosleep({tv_sec=0, tv_nsec=10000000}, NULL) = 0
[pid 12417] nanosleep({tv_sec=0, tv_nsec=10000000}, NULL) = 0

这里不断的打印 nanosleep,就是我们程序中的 usleep 调用的结果,但是这里有个问题,为什么 printf 的打印没有相关的 write 系统调用?

仔细查看终端输出,发现了如下信息:

[pid 12417] write(1, " times\nthread running 23319 time"..., 4096) = 4096

可以看出,这里向描述符 stdout 写入了 4096 个字符,printf 并不是没有执行 write 系统调用,只是它内部存在缓冲,这里明显是填满了缓冲区才执行系统调用,这种方式减少了系统调用的次数,提高了效率。

回到最初的问题

根据上文中的描述与测试程序的测试结果,确定之前遇到的 strace -p 没有追踪到系统调用的问题是没有添加 -f 选项,导致 strace 只跟踪 thread id 等于 PID 的线程,这个线程一般是领头线程,它正好阻塞的话,当然就没有系统调用执行喽,也就不会跟踪到喽。

思考与总结

能够被这种问题阻塞,只能说明我们对 strace 的一些细节功能并不是太清楚。

其实这个问题并没有太复杂,只需要使用 strace -p PID -f -t 来追终 server 的所有线程、子进程的系统调用及其时间,然后与系统中 dmesg 信息 netlink: xxx 打印的时间对比,当下一次出现问题的时候就能够找到出问题的点,这样这个问题就变得非常简单了。

可是正因为我们对 strace 的功能存在盲区,不得不从内核入手,最终却学习到了更多的知识,真是塞翁失马,焉知非福。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
非常感谢您的指正,我的回答中有误。以下是关于strace的正确介绍和使用方法: strace是一种调试工具,可以用来跟踪进程的系统调用以及接收和发送的信号。它可以用来定位程序问题,例如执行错误的系统调用、I/O错误等等。下面介绍如何使用strace: 1. 安装strace 在大多数Linux发行版中,strace是默认安装的。如果您的系统中没有安装strace,可以使用以下命令进行安装: ``` sudo apt-get install strace ``` 2. 使用strace 使用strace非常简单,只需要在终端中输入strace命令,加上需要跟踪的进程ID即可。例如,假设需要跟踪进程ID为1234的进程,可以使用以下命令: ``` sudo strace -p 1234 ``` strace会输出进程执行的每个系统调用,以及调用时传递的参数和返回值。例如,以下是一个strace的输出示例: ``` open("/etc/passwd", O_RDONLY) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=2455, ...}) = 0 mmap(NULL, 2455, PROT_READ, MAP_SHARED, 3, 0) = 0x7f70a0346000 close(3) = 0 ``` 其中,每一行都是一个系统调用,以及调用时的参数和返回值。在上面的示例中,进程调用了open系统调用来打开/etc/passwd文件,然后使用fstat系统调用查询文件的状态,并使用mmap系统调用将文件映射到内存中,最后使用close系统调用关闭文件。 可以使用strace的不同选项来进行更详细的设置,例如使用“-t”选项来在输出中添加时间戳,使用“-e”选项来指定需要跟踪系统调用等等。具体可以参考strace的官方文档。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值