首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Linux strace、pstack 命令 使用详解

strace简介

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

strace 底层使用内核的 ptrace 特性来实现其功能。

在运维的日常工作中,故障处理和问题诊断是个主要的内容,也是必备的技能。strace 作为一种动态跟踪工具,能够帮助运维高效地定位进程和服务故障。它像是一个侦探,通过系统调用的蛛丝马迹,告诉你异常的真相。

starce参数

-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执行被跟踪的命令

starce用法

root@ubuntu:/home/ubuntu# strace cat /dev/null

execve("/bin/cat", ["cat", "/dev/null"], 0x7fffc1400888 /* 25 vars */) = 0

brk(NULL) = 0x563ac0baf000

access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)

access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)

openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3

fstat(3, {st_mode=S_IFREG|0644, st_size=83275, ...}) = 0

mmap(NULL, 83275, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f98774c8000

close(3) = 0

access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)

openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3

read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\240\35\2\0\0\0\0\0"..., 832) = 832

fstat(3, {st_mode=S_IFREG|0755, st_size=2030928, ...}) = 0

mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f98774c6000

mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f9876ec3000

mprotect(0x7f98770aa000, 2097152, PROT_NONE) = 0

mmap(0x7f98772aa000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f98772aa000

mmap(0x7f98772b0000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f98772b0000

close(3) = 0

arch_prctl(ARCH_SET_FS, 0x7f98774c7540) = 0

mprotect(0x7f98772aa000, 16384, PROT_READ) = 0

mprotect(0x563ac0a25000, 4096, PROT_READ) = 0

mprotect(0x7f98774dd000, 4096, PROT_READ) = 0

munmap(0x7f98774c8000, 83275) = 0

brk(NULL) = 0x563ac0baf000

brk(0x563ac0bd0000) = 0x563ac0bd0000

openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3

fstat(3, {st_mode=S_IFREG|0644, st_size=3004224, ...}) = 0

mmap(NULL, 3004224, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f9876be5000

close(3) = 0

fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0

openat(AT_FDCWD, "/dev/null", O_RDONLY) = 3

fstat(3, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 3), ...}) = 0

fadvise64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0

mmap(NULL, 139264, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f98774a4000

read(3, "", 131072) = 0

munmap(0x7f98774a4000, 139264) = 0

close(3) = 0

close(1) = 0

close(2) = 0

exit_group(0) = ?

+++ exited with 0 +++

root@ubuntu:/home/ubuntu#

每一行都是一条系统调用,等号左边是系统调用的函数名及其参数,右边是该调用的返回值。

strace 显示这些调用的参数并返回符号形式的值。strace 从内核接收信息,而且不需要以任何特殊的方式来构建内核

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

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

以“定位一次系统无法解析域名故障”为例

【问题现象】:

无法访问外网域名,提示Name or service not know。

且已检查系统DNS配置文件/etc/resolv.conf正确,排除DNS解析失败。

【问题分析】:

当前无法确定系统在执行 解析域名失败的原因,这时候需要使用strace查看系统调用过程,域名解析通常跟系统读取文件相关,因此我们只查看open file的过程。具体命令如下:

如上图所示在系统调用过程中出现/usr/lib64/libnss_dns.so.2文件缺失,则问题根因已确定为libnss_dns.so.2系统库文件缺失。

pstack 命令

pstack是一个脚本工具,其核心实现就是使用了gdb以及thread apply all bt命令,其脚本实现如下:

#!/bin/sh

if test $# -ne 1; then

echo "Usage: `basename $0 .sh` " 1>&2

exit 1

fi

if test ! -r /proc/$1; then

echo "Process $1 not found." 1>&2

exit 1

fi

# GDB doesn't allow "thread apply all bt" when the process isn't

# threaded; need to peek at the process to determine if that or the

# simpler "bt" should be used.

backtrace="bt"

if test -d /proc/$1/task ; then

# Newer kernel; has a task/ directory.

if test `/bin/ls /proc/$1/task | /usr/bin/wc -l` -gt 1 2>/dev/null ; then

backtrace="thread apply all bt"

fi

elif test -f /proc/$1/maps ; then

# Older kernel; go by it loading libpthread.

if /bin/grep -e libpthread /proc/$1/maps > /dev/null 2>&1 ; then

backtrace="thread apply all bt"

fi

fi

GDB=${GDB:-/usr/bin/gdb}

if $GDB -nx --quiet --batch --readnever > /dev/null 2>&1; then

readnever=--readnever

else

readnever=

fi

# Run GDB, strip out unwanted noise.

$GDB --quiet $readnever -nx /proc/$1/exe $1

set width 0

set height 0

set pagination no

$backtrace

EOF

/bin/sed -n \

-e 's/^\((gdb) \)*//' \

-e '/^#/p' \

-e '/^Thread/p'

#end

pstack用法

root@ubuntu:/home/ubuntu# ./pstack 1534

Thread 10 (Thread 0x7f1efb7fe700 (LWP 1570)):

#0 0x00007f1f434e4ad3 in pthread_cond_wait@@GLIBC_2.3.2 () at /lib/x86_64-linux-gnu/libpthread.so.0

#1 0x000000000000009d in ()

#2 0x0000000100000081 in ()

#3 0x0000555cae4dd474 in ()

#4 0x00007f1efb7fd9e0 in ()

#5 0x0000008000000000 in ()

#6 0x00007f1efb7fda00 in ()

#7 0x0000000000000001 in ()

#8 0x00007f1f3bc24c61 in () at /usr/lib/x86_64-linux-gnu/libmozjs-52.so.0

#9 0x00007f1f434e4770 in __condvar_cleanup_waiting () at /lib/x86_64-linux-gnu/libpthread.so.0

#10 0x00007f1efb7fda00 in ()

#11 0x00007f1efb7fda60 in ()

#12 0x0000000000000000 in ()

Thread 9 (Thread 0x7f1efbfff700 (LWP 1569)):

..............

strace和pstack使用

有时我们需要对程序进行优化、减少程序响应时间。除了一段段地对代码进行时间复杂度分析,我们还有更便捷的方法吗?

若能直接找到影响程序运行时间的函数调用,再有针对地对相关函数进行代码分析和优化,那相比漫无目的地看代码,效率就高多了。

将strace和pstack工具结合起来使用,就可以达到以上目的。strace跟踪程序使用的底层系统调用,可输出系统调用被执行的时间点以及各个调用耗时;pstack工具对指定PID的进程输出函数调用栈。

下面我们通过一个简单的消息收发程序,说明使用strace、pstack进行程序分析的具体方法。

程序说明

该程序是一个简单的socket程序,由server/client组成。server端监听某端口,等待client的连接,client连接server后定时向server发送消息,server每接收一条消息后向client发送响应消息。程序server与client交互如下图示:

在程序运行起来之后,发现server接收到client的submit消息之后,需要较长时间才发出resp响应。通过tcpdump抓包发现,time2与time1的时间间隔在1s左右:

由上初步分析可知,消息响应慢是server端程序问题。下面我们来看如何使用strace和pstack分析server端程序响应慢的原因。

strace查看系统调用

首先我们拉起server/client程序,并使用strace对server进程进行跟踪:

稍等一段时间之后,我们将strace停掉, server.strace文件中有以下输出:

可以看到server接收数据之后(对应recvfrom调用),经过1s左右时间将消息发出(对应sendto调用),从响应时间看,与抓包的结果吻合。又可以看出nanosleep系统调用耗费了1s时间。

因而可以断定响应延时由nanosleep对应的函数调用造成。

那具体是哪一个函数调用呢?在strace输出结果中并不能找到答案,因其输出显示都是系统调用,要显示程序中函数调用栈信息,就轮到pstack上场了。

pstack查看函数堆栈

pstack是一个脚本工具,其核心实现就是使用了gdb以及thread apply all bt命令,下面我们使用pstack查看server进程函数堆栈:

从以上信息可以看出,函数调用关系为:main->ha_ha->sleep,因而我们可以找到ha_ha函数进行分析和优化修改。

小结

本文通过一个server/client程序事例,说明了使用strace和pstack分析响应延时的方法。

由最初server端响应慢现象,到使用strace跟踪出具体耗时的系统调用,再到使用pstack查到程序中具体的耗时函数,一步步找到了影响程序运行时间的程序代码。

更多地了解底层,从操作系统层面着手,更有助于程序性能分析与优化。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OUedP2_nZoeIUnuTLU6M00HA0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券