HeLei Blog

在线定位问题常用命令

有时候,有很多问题只有在线上或者预发环境才能发现,而线上又不能调试代码,所以线
上问题定位就只能看日志、系统状态和dump线程,介绍一些常用的工具,介绍一些常用命令定位线上问题。

top

在Linux命令行下使用TOP命令查看每个进程的情况,显示如下。
img01

  • 交互命令数字1查看每个CPU的性能数据
  • 查看某个进程所有线程的信息
    1
    top -H -p pid

strace & pstack

strace就是这样一款工具。通过它,我们可以跟踪程序执行过程中产生的系统调用及接收到的信号,帮助我们分析程序或命令执行中遇到的异常情况。

1. 一个简单的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//main.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main( )
{
  int fd ;
  int i = 0 ;
  fd = open( “/tmp/foo”, O_RDONLY ) ;
  if ( fd < 0 )
    i=5;
  else
    i=2;
  return i;
}

2.strace跟踪输出

使用以下命令,我们将使用strace对以上程序进行跟踪,并将结果重定向至main.strace文件:

1
$ strace -o main.strace ./main

接下来我们来看main.strace文件的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
lx@LX:~$ cat main.strace
execve("./main", ["./main"], [/* 43 vars */]) = 0
brk(0) = 0x9ac4000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7739000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=80682, ...}) = 0
mmap2(NULL, 80682, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7725000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\220o\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1434180, ...}) = 0
mmap2(NULL, 1444360, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x56d000
mprotect(0x6c7000, 4096, PROT_NONE) = 0
mmap2(0x6c8000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x15a) = 0x6c8000
mmap2(0x6cb000, 10760, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x6cb000
close(3) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7724000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb77248d0, limit:1048575, seg_32bit:1, contents:0, read_exec_ only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0x6c8000, 8192, PROT_READ) = 0
mprotect(0x8049000, 4096, PROT_READ) = 0
mprotect(0x4b0000, 4096, PROT_READ) = 0
munmap(0xb7725000, 80682) = 0
open("/tmp/foo", O_RDONLY) = -1 ENOENT (No such file or directory)
exit_group(5) = ?
//标红的行号为方便说明而添加,非strace执行输出

strace跟踪程序与系统交互时产生的系统调用,以上每一行就对应一个系统调用,格式为:

系统调用的名称( 参数… ) = 返回值 错误标志和描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Line 1: 对于命令行下执行的程序,execve(或exec系列调用中的某一个)均为strace输出系统调用中的第一个。strace首先调用fork或clone函数新建一个子进程,然后在子进程中调用exec载入需要执行的程序(这里为./main)
Line 2: 以0作为参数调用brk,返回值为内存管理的起始地址(若在子进程中调用malloc,则从0x9ac4000地址开始分配空间)
Line 3: 调用access函数检验/etc/ld.so.nohwcap是否存在
Line 4: 使用mmap2函数进行匿名内存映射,以此来获取8192bytes内存空间,该空间起始地址为0xb7739000,关于匿名内存映射,可以看这里
Line 6: 调用open函数尝试打开/etc/ld.so.cache文件,返回文件描述符为3
Line 7: fstat64函数获取/etc/ld.so.cache文件信息
Line 8: 调用mmap2函数将/etc/ld.so.cache文件映射至内存,关于使用mmap映射文件至内存,可以看这里
Line 9: close关闭文件描述符为3指向的/etc/ld.so.cache文件
Line12: 调用read,从/lib/i386-linux-gnu/libc.so.6该libc库文件中读取512bytes,即读取ELF头信息
Line15: 使用mprotect函数对0x6c7000起始的4096bytes空间进行保护(PROT_NONE表示不能访问,PROT_READ表示可以读取)
Line24: 调用munmap函数,将/etc/ld.so.cache文件从内存中去映射,与Line 8的mmap2对应
Line25: 对应源码中使用到的唯一的系统调用——open函数,使用其打开/tmp/foo文件
Line26: 子进程结束,退出码为5(为什么退出值为5?返回前面程序示例部分看看源码吧:)

3. 输出分析

呼呼!看完这么多系统调用函数,是不是有点摸不着北?让我们从整体入手,回到主题strace上来。

从上面输出可以发现,真正能与源码对应上的只有open这一个系统调用(Line25),其他系统调用几乎都用于进行进程初始化工作:装载被执行程序、载入libc函数库、设置内存映射等。

源码中的if语句或其他代码在相应strace输出中并没有体现,因为它们并没有唤起系统调用。strace只关心程序与系统之间产生的交互,因而strace不适用于程序逻辑代码的排错和分析。

4. 常用选项

  1. 跟踪子进程 -f
  2. 记录系统调用时间

strace还可以记录程序与系统交互时,各个系统调用发生时的时间信息,有r、t、tt、ttt、T等几个选项,它们记录时间的方式为:

-T: 记录各个系统调用花费的时间,精确到微秒

-r: 以第一个系统调用(通常为execve)计时,精确到微秒

-t: 时:分:秒

-tt: 时:分:秒 . 微秒

-ttt: 计算机纪元以来的秒数 . 微秒

比较常用的为T选项,因为其提供了每个系统调用花费时间。而其他选项的时间记录既包含系统调用时间,又算上用户级代码执行用时,参考意义就小一些。对部分时间选项我们可以组合起来使用,例如:

1
2
3
4
5
6
7
8
9
10
11
strace -Tr ./main
0.000000 execve(“./main”, [“main”], [/* 64 vars */]) = 0
0.000931 fcntl64(0, F_GETFD)= 0 <0.000012>
0.000090 fcntl64(1, F_GETFD)= 0 <0.000022>
0.000060 fcntl64(2, F_GETFD)= 0 <0.000012>
0.000054 uname({sys=”Linux”, node=”ion”, ...}) = 0 <0.000014>
0.000307 geteuid32()= 7903 <0.000011>
0.000040 getuid32()= 7903 <0.000012>
0.000039 getegid32()= 200 <0.000011>
0.000039 getgid32()= 200 <0.000011>
……

最左边一列为-r选项对应的时间输出,最右边一列为-T选项对应的输出。

  1. 跟踪正在运行的进程 strace -p PID

5. 使用strace处理程序挂死实例

挂死程序源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//hang.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char** argv)
{
getpid(); //该系统调用起到标识作用
if(argc < 2)
{
printf("hang (user|system)\n");
return 1;
}
if(!strcmp(argv[1], "user"))
while(1);
else if(!strcmp(argv[1], "system"))
sleep(500);
return 0;
}

可向该程序传送user和system参数,以上代码使用死循环模拟用户态挂死,调用sleep模拟内核态程序挂死。

strace跟踪输出

  • 用户态挂死跟踪输出:

    1
    2
    3
    4
    5
    ……
    mprotect(0x8049000, 4096, PROT_READ) = 0
    mprotect(0xb59000, 4096, PROT_READ) = 0
    munmap(0xb77bf000, 80682) = 0
    getpid() = 14539
  • 内核态挂死跟踪输出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ……
    mprotect(0x8049000, 4096, PROT_READ) = 0
    mprotect(0xddf000, 4096, PROT_READ) = 0
    munmap(0xb7855000, 80682) = 0
    getpid() = 14543
    rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
    rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
    rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
    nanosleep({500, 0},

输出分析

用户态挂死情况下,strace在getpid()一行输出之后没有其他系统调用输出;进程在内核态挂死,最后一行的系统调用nanosleep不能完整显示,这里nanosleep没有返回值表示该调用尚未完成。

因而我们可以得出以下结论:使用strace跟踪挂死程序,如果最后一行系统调用显示完整,程序在逻辑代码处挂死;如果最后一行系统调用显示不完整,程序在该系统调用处挂死。

其它命令

  • 查看网络流量 /proc/net/dev
    img02
  • 查看连接数 /proc/net/snmp
    img03
  • 查看内存信息 /proc/meminfo
    img04
坚持原创技术分享,您的支持将鼓励我继续创作!