Linux如何定位函数热点函数调用栈
一、前言
最近再看一个问题,发现系统中的某一个进程CPU占用很高,达到百分之100的CPU占用,这个时候需要找到该进程的热点函数以及调用栈(由于涉及到公司的保密以及安全问题,所以我自己构造了一个程序来实战调试,以便于理解),从而去排查代码。我使用如下代码进行调试。这个程序是一个死循环程序,运行起来后,你会发现CPU的占用会达到百分之100。
#include <stdio.h>
int test_perf_top(int i, int j)
{
int c = i+j;
int tmp;
while(1)
{
tmp++;
if(tmp>c){
tmp=0;
}
}
}
int main(int argc, char *argv[])
{
printf("start test\n");
test_perf_top(1000, 2000);
return 1;
}
使用如下命令进行编译:
gcc main.c
二、使用perf top查看热点函数
关于perf top的详细使用方法,这里不再阐述,详细用户请查看perf的man page.
首先我们运行程序a.ou
t, 然后用top先查看下进程状态:可以看到a.out这个
进程的cpu
占用是百分之100.
接下来我们用perf top看下这个进程到底在干什么事情:
perf top -p 2341 -g
由上图可以看到,这个a.out
这个进程在test_perf_top这个函数花费了大部分时间,这个时候我们去查看这个函数的代码,找出有可能出现死循环的代码分析,从而解决问题。
三、使用GDB调试
当然这个问题也可以用gdb
来进行调试
gdb -p 2341
如果进程是多线程,那么请先用top命令查看出是哪一个线程占用cpu
高。
执行bt
可以查看当前进程的调用栈。从而推断进程在频繁执行test_perf_top
函数
四、没有符号表,如何处理
前面两种方法都是在有符号表的情况下进行的,但是假如可执行程序被strip掉符号表之后,那么无论是gdb
还是perf top
打印出来的都是一串16进制的字符串, 这不利于我们进一步排查问题,特别是操作系统开启了地址随机化、编译程序的时候将程序编译成位置无关。那么实际上加载到内核执行的程序的地址和二进制的可执行程序中的地址是对应不上的。这个时候难道就没有办法了吗?当然是有的。我们可以用gdb
打印出热点函数代码段前后20条汇编代码, 然后用objdump
也反汇编出可执行程序(这个可执行程序最好重新编译,带上符号表)的汇编代码。然后用beyond compare
这种软件进行对比汇编代码,基本上都可以定位到具体的函数。
我们首先用file命令看下a.out
是否带有符号表:
由于我们前面使用gcc
编译的时候并没有去符号表,所以显示是not striped
我们用strip 命令将a.out
的符号表去掉。strip a.out
现在符号表去掉了:我们重新运行下程序:进程号变为了2881
perf top
和gdb
来看一下
首先是perf top -p 2881 -g
:
完全不知道是什么鬼。光看这个信息是看不出来的;
接着我用gdb
看一下:
gdb -p 2881
从gdb
的输出来看,我也看不出是什么鬼。符号看不出,还有汇编啊,像我这种懒惰分子,除非使用到汇编代码,否则一行一行去看汇编代码,我是不愿意的。
第一步、 我先用gdb
里面的 x命令打印出这个0x0000000000400556
地址附近的代码(前后都最好打印一些汇编,这样beyond compare比较好匹配):
注意看,我为了打印当前pc
指针前后的几十条汇编,所以我故意把打印开始的地址变成了0x0000000000400536
,而不是
0x0000000000400556
, 将这些汇编复制到beyond compare的左边,等待比较。
第二步、重新编译下原程序,不要去掉符号表,然后反汇编。
objdump -d a.out
输出如下汇编:
然后把输出的汇编代码拷贝到beyond compare的右边,比较:
这样我们就看出了是在test_perf_top
这个函数中执行了代码,于是就可以定位到出问题的代码。然后去分析这个函数就可以了。
五、strace
的使用
构造的这个例子并不适合使用strace
进行调试,因为他没有系统调用,但是一般情况下我们写的一些网络服务都是使用select
框架或者是epoll
框架,然后调用read
等系统调用去收发数据包, 这类进程,我们还可以通过strace
调试某一个进程,然后看出哪些系统调用在频繁调用,唤醒事件是否符合预期,来进一步调试问题。