Linux 程序异常诊断工具(pstack与strace命令使用详解、死锁)

135次阅读
没有评论

原文:Linux 程序异常诊断工具(pstack 与 strace 命令使用详解、死锁)_pstack strace_herryone123 的博客 -CSDN 博客

strace 跟踪程序使用的底层系统调用,可输出系统调用被执行的时间点以及各个调用耗时;pstack 工具对指定 PID 的进程输出函数调用栈。

一、strace

1.1 基本概念

 strace 是一个可用于诊断、调试和教学的 Linux 用户空间跟踪器。我们用它来监控用户空间进程和内核的交互,比如系统调用、信号传递、进程状态变更等。

strace 常用来 跟踪进程执行时的系统调用和所接收的信号 。在 Linux 世界, 进程不能直接访问硬件设备 ,当进程需要访问硬件设备(比如读取磁盘文件,接收网络数据等等) 时,必须由用户态模式切换至内核态模式,通 过系统调用访问硬件设备。strace 可以跟踪到一个进程产生的系统调用, 包括参数,返回值,执行消耗的时间。

1.2 使用方法

strace -o output.txt -T -tt -e trace=all -p 28979

  跟踪 28979 进程的所有系统调用(-e trace=all),并统计系统调用的花费时间,以及开始时间(并以可视化的时分秒格式显示),最后将记录结果存在 output.txt 文件里面。

参数:

-c 统计每一系统调用的所执行的时间, 次数和出错的次数等.
-d 输出 strace 关于标准错误的调试信息.
-f 跟踪由 fork 调用所产生的子进程.
-ff 如果提供 -o filename, 则所有进程的跟踪结果输出到相应的 filename.pid 中,pid 是各进程的进程号.
-F 尝试跟踪 vfork 调用. 在 - f 时,vfork 不被跟踪.
-h 输出简要的帮助信息.
-i 输出系统调用的入口指针.
-q 禁止输出关于脱离的消息.
-r 打印出相对时间关于,, 每一个系统调用.
-t 在输出中的每一行前加上时间信息.
-tt 在输出中的每一行前加上时间信息, 微秒级.
-ttt 微秒级输出, 以秒了表示时间.
-T 显示每一调用所耗的时间.
-v 输出所有的系统调用. 一些调用关于环境变量, 状态, 输入输出等调用由于使用频繁, 默认不输出.
-V 输出 strace 的版本信息.
-x 以十六进制形式输出非标准字符串
-xx 所有字符串以十六进制形式输出.
-a column
设置返回值的输出位置. 默认 为 40.
-e expr
指定一个表达式, 用来控制如何跟踪. 格式如下:
[qualifier=][!]value1[,value2]…
qualifier 只能是 trace,abbrev,verbose,raw,signal,read,write 其中之一.value 是用来限定的符号或数字. 默认的 qualifier 是 trace. 感叹号是否定符号. 例如:
-eopen 等价于 -e trace=open, 表示只跟踪 open 调用. 而 -etrace!=open 表示跟踪除了 open 以外的其他调用. 有两个特殊的符号 all 和 none.
注意有些 shell 使用! 来执行历史记录里的命令, 所以要使用 \.
-e trace=set
只跟踪指定的系统 调用. 例如:-e trace=open,close,rean,write 表示只跟踪这四个系统调用. 默认的为 set=all.
-e trace=file
只跟踪有关文件操作的系统调用.
-e trace=process
只跟踪有关进程控制的系统调用.
-e trace=network
跟踪与网络有关的所有系统调用.
-e strace=signal
跟踪所有与系统信号有关的 系统调用
-e trace=ipc
跟踪所有与进程通讯有关的系统调用
-e abbrev=set
设定 strace 输出的系统调用的结果集.-v 等与 abbrev=none. 默认为 abbrev=all.
-e raw=set
将指 定的系统调用的参数以十六进制显示.
-e signal=set
指定跟踪的系统信号. 默认为 all. 如 signal=!SIGIO(或者 signal=!io), 表示不跟踪 SIGIO 信号.
-e read=set
输出从指定文件中读出 的数据. 例如:
-e read=3,5
-e write=set
输出写入到指定文件中的数据.
-o filename
将 strace 的输出写入文件 filename
-p pid
跟踪指定的进程 pid.
-s strsize
指定输出的字符串的最大长度. 默认为 32. 文件名一直全部输出.
-u username
以 username 的 UID 和 GID 执行被跟踪的命令

1.3 应用举例

该部分应用参考参考文献【2】。

查看程序 server

# ps -elf | grep server | grep -v grep  
0 S root 16739 22642 0 76 0 - 634 1024 14:26 pts/2 00:00:00 ./server  
# strace -o server.strace -Ttt -p 16739  
Process 16739 attached - interrupt to quit 

 一段时间后,停止,查看 servers.strace

  1. 14:46:39.741366 select(8, [3 4], NULL, NULL, {1, 0}) = 1 (in [4], left {0, 1648}) <0.998415>
  2. 14:46:40.739965 recvfrom(4, "hello", 6, 0, NULL, NULL) = 5 <0.000068>
  3. 14:46:40.740241 write(1, "hello\n", 6) = 6 <0.000066>
  4. 14:46:40.740414 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 <0.000046>
  5. 14:46:40.740565 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 <0.000048>
  6. 14:46:40.740715 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 <0.000046>
  7. 14:46:40.740853 nanosleep({1, 0}, {1, 0}) = 0 <1.000276>
  8. 14:46:41.741284 sendto(4, "hello\0", 6, 0, NULL, 0) = 6 <0.000111>

由此知程序耗时在 nanosleep.

二、pstack

2.1 基本概念

pstack 就是由 gdb 执行的 shell 脚本。用于打印正在运行的进程的栈跟踪信息。它能对潜在的死锁予以提示, 而 pstack 只提供了线索, 需要 gdb 进一步的确定。

2.2 使用方法

pstack 是 gdb 的一部分。此命令允许使用的唯一选项是要检查的进程的 PID

ps auxf | grep <process_name>
pstack $pid   

pstack 可以打印出该进程的所有线程的情况,那它自然就可以用来检测死锁。

该部分转载参考文献【1】,写得很详细:

(1)产生死锁的代码

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

pthread_mutex_t mutex_1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex_2 = PTHREAD_MUTEX_INITIALIZER;

void *thread1_proc(void *data)
{pthread_mutex_lock(&mutex_1);
    sleep(1);
    pthread_mutex_lock(&mutex_2);

    pthread_mutex_unlock(&mutex_2);
    pthread_mutex_unlock(&mutex_1);
    return (void *)0;
}

void *thread2_proc(void *data)
{pthread_mutex_lock(&mutex_2);
    sleep(1);
    pthread_mutex_lock(&mutex_1);

    pthread_mutex_unlock(&mutex_1);
    pthread_mutex_unlock(&mutex_2);
    return (void *)0;
}

int main()
{
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, thread1_proc, NULL);
    pthread_create(&tid2, NULL, thread2_proc, NULL);
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}

(2)编译及运行程序:

gcc -g -Wall -Werror dead_lock.c -pthread -o test

./test            // 则进程死锁一直卡住了

pstack $pid 

Thread 3 (Thread 0x7f0489d8e710 (LWP 3616)):
#0  0x0000003c0f60e034 in __lll_lock_wait () from /lib64/libpthread.so.0
#1  0x0000003c0f609345 in _L_lock_870 () from /lib64/libpthread.so.0
#2  0x0000003c0f609217 in pthread_mutex_lock () from /lib64/libpthread.so.0
#3  0x000000000040068e in thread1_proc ()
#4  0x0000003c0f6077e1 in start_thread () from /lib64/libpthread.so.0
#5  0x0000003c0f2e153d in clone () from /lib64/libc.so.6
Thread 2 (Thread 0x7f048938d710 (LWP 3617)):
#0  0x0000003c0f60e034 in __lll_lock_wait () from /lib64/libpthread.so.0
#1  0x0000003c0f609345 in _L_lock_870 () from /lib64/libpthread.so.0
#2  0x0000003c0f609217 in pthread_mutex_lock () from /lib64/libpthread.so.0
#3  0x00000000004006d3 in thread2_proc ()
#4  0x0000003c0f6077e1 in start_thread () from /lib64/libpthread.so.0
#5  0x0000003c0f2e153d in clone () from /lib64/libc.so.6
Thread 1 (Thread 0x7f0489d90700 (LWP 3615)):
#0  0x0000003c0f60803d in pthread_join () from /lib64/libpthread.so.0
#1  0x000000000040073d in main ()

Thread 2 和 Thread 3 都在等待锁,就是等待别人释放自己想要锁的那把锁,但是并不能看出来是否是死锁,继续使用 gdb 分析。

(3)使用 GDB 分析

# gdb -p $pid

# info thread// 打印所有的线程信息

  3 Thread 0x7f645eb23710 (LWP 3687)  0x0000003c0f60e034 in __lll_lock_wait () from /lib64/libpthread.so.0
  2 Thread 0x7f645e122710 (LWP 3688)  0x0000003c0f60e034 in __lll_lock_wait () from /lib64/libpthread.so.0
* 1 Thread 0x7f645eb25700 (LWP 3686)  0x0000003c0f60803d in pthread_join () from /lib64/libpthread.so.0

* 表示 gdb 锁定的线程,切换到第二个线程去查看

# thread 2          // 切换到第 2 个线程, 可以看到线程 id 为 0x7f645e122710, 而LWP 指定的值是 gdb 用来唯一标示该进程中线程的,便于调试的时候追踪

[Switching to thread 2 (Thread 0x7f645e122710 (LWP 3688))]#0  0x0000003c0f60e034 in __lll_lock_wait ()  

# bt                   // bt 可以打印函数堆栈,却无法看到函数参数,#0  0x0000003c0f60e034 in __lll_lock_wait () from /lib64/libpthread.so.0
#1  0x0000003c0f609345 in _L_lock_870 () from /lib64/libpthread.so.0
#2  0x0000003c0f609217 in pthread_mutex_lock () from /lib64/libpthread.so.0
#3  0x00000000004006d3 in thread2_proc (data=0x0) at dead_lock.c:23
#4  0x0000003c0f6077e1 in start_thread () from /lib64/libpthread.so.0
#5  0x0000003c0f2e153d in clone () from /lib64/libc.so.6

# frame 3      // 打印第三帧信息(#3). 每次函数调用都会有压栈的过程,而 frame 则记录栈中的帧信息

#3  0x00000000004006d3 in thread2_proc (data=0x0) at dead_lock.c:23
23     pthread_mutex_lock(&mutex_1);

 # p mutext_1  // 打印 mutex_1 的值 ,  __owner 表示 gdb 中标示线程的值,即 LWP

$1 = {__data = {__lock = 2, __count = 0, __owner = 3687, __nusers = 1, __kind = 0, __spins = 0, __list = {__prev = 0x0,
      __next = 0x0}}, __size = "\002\000\000\000\000\000\000\000g\016\000\000\001", ‘\000’ <repeats 26 times>, __align = 2}

分析另一个线程 3

# thread 3

# frame 3

# p mutex_2

$2 = {__data = {__lock = 2, __count = 0, __owner = 3688, __nusers = 1, __kind = 0, __spins = 0, __list = {__prev = 0x0,
      __next = 0x0}}, __size = "\002\000\000\000\000\000\000\000h\016\000\000\001", ‘\000’ <repeats 26 times>, __align = 2}

LWP(3688)在等待 LWP(3687)所拥有的 mutex_1, 而同时 LWP(3687)又在等待 LWP(3688)所拥有的 mutex_2, 死锁。

三、死锁的概念

3.1 死锁概念

互斥锁是保护临界资源被线程间(或进程间)互斥的访问临界资源,当一个线程得到锁不释放时另一个线程申请时必须等待。当多个线程因为竞争资源而造成的一种僵局(互相等待),如果不施以援手,这些进程将永远等待。

3.2 产生条件

① 系统资源不足:系统中所拥有的资源其数量不足以满足线程运行的需要,使得在运行过程中,因争夺资源而陷入僵局。
② 线程间推进顺序非法:线程间在运行过程中,申请和释放的顺序不合法。
③ 资源分配不当。

四、常用工具

Linux 程序异常诊断工具(pstack 与 strace 命令使用详解、死锁)

参考文献

  1. linux 命令之 pstack:http://www.voidcn.com/article/p-tigeosxb-ph.html
  2. Linux strace、pstack 命令 使用详解:https://blog.csdn.net/joeyon1985/article/details/72986412
  3. linux 命令 – pstack 命令(跟踪进程栈)https://www.cnblogs.com/kongzhongqijing/articles/7685699.html
  4. Linux strace 命令https://www.cnblogs.com/ggjucheng/archive/2012/01/08/2316692.html
  5. Linux strace 命令详解:https://www.linuxidc.com/Linux/2012-12/75671.htm
  6. Linux— 死锁及避免死锁的方法: https://blog.csdn.net/qq_37934101/article/details/81869245
  7. CPU Flame Graphs – Brendan Gregg
正文完
 
评论(没有评论)