观察运行时堆栈【原创】

原创,装载请标明引用地址,欢迎拍砖

      

1. 背景

       嘉龙在bprofile函数级别性能测试中抱怨:没有针对每个函数消耗cpu时间的统计。于是据此我做了一点研究:
       (1)    bprofile实际上是取N个时间点进行抽样统计,如果第i个时间点运行时栈中有本函数,则认为函数被调用一次。因此,bprofile报告中虽然说是“函数调用次数”,但实际上是“函数占用cpu时间”
       (2)    运行时监控某进程的问题:
       如果不在编译时对源代码做静态分析,而是在程序已经跑起来一会运行时对其监控,需要考虑哪些问题呢?
       A.    如何获取进程中用到的变量和函数:
       通过分析进程的“符号表”(变量和函数的符号名),且需要仔细研究“名称修饰”,恢复符号在源代码的原本定义;
       B.    如何在运行时追踪某“函数被调用”的情况呢:
              B.1 需要观察“运行时栈”,当函数A调用函数B时,参数以及A函数地址会被压栈,因此如果可以知道“运行时栈”中返回地址在哪儿,可以反查“符号表”,知道A函数被调用了。但是如何知道函数B正在被调用呢?
              B.2 通常只能用gdb启动进程,然后观察运行时的“运行时栈”情况,但如果进程已经起来了(并且不是用gdb起来的),需要解决:如何从外部获取一个进程虚拟地址空间的信息?就我的知识,答案是:不能!回过头来想,bprofile对程序进行函数级别性能测试也需要用cpuprofile.sh去启动的,而不是等程序已经启动起来了,再去统计$cpuprof.sh -r ./bin/crsui
       结论:
       ——如果进程已经启动(不是用我们编写的性能测试工具启动的),此时无论如何也不能对其进行函数级别性能测试——因为无法获取该进程虚拟地址空间的信息
但很不幸,后来找到一个工具gstack可以观察运行时栈(gstack pid),但还不清楚其原理


       ——理论上,可以反编译可执行文件(ELF),对汇编代码进行插桩(函数调用入口和出口统计调用时间,累加到hash表),统计每个函数的调用时间;汇编代码中,函数调用采用的是相对地址/绝对地址(长jmp/短jmp),而非函数名,但或许可以从符号表中反查出其函数名(binutils里面工具c++filt)。


2. C/C++中运行时堆栈分析

    gdb : 常规方法,使用gdb进行调试,然后观察“运行时栈”信息

    gstack : 但如果一个进程已经启动,再要观察他的“运行时栈”信息,可以用gstack pid。举例:

写一个小程序,涉及“运行时栈”信息,如下test.cpp

扫描二维码关注公众号,回复: 1240879 查看本文章
#include<iostream>
using namespace std;

void fun(int i){
    cout<<"fun("<<i<<")"<<endl;
    sleep(2);
}

int main(){
    int i=2;
    while(true){
        fun(i++);
        sleep(2);
    }
}

 编译并后台运行

g++ -o test test.cpp
nohup ./test & 

通过gstack pid可以观察到如下信息

[[email protected] stack_trace]$ gstack 11583
#0  0x000000302af8f172 in __nanosleep_nocancel () from /lib64/tls/libc.so.6
#1  0x000000302af8f010 in sleep () from /lib64/tls/libc.so.6
#2  0x0000000000400a9d in main ()
[[email protected] stack_trace]$ gstack 11583
#0  0x000000302af8f172 in __nanosleep_nocancel () from /lib64/tls/libc.so.6
#1  0x000000302af8f010 in sleep () from /lib64/tls/libc.so.6
#2  0x0000000000400a71 in fun(int) ()
#3  0x0000000000400a93 in main ()

通过在源代码中打印出运行时栈的方法,但需要保证<exeinfo.h>(今天时间太紧,没空深究),否则报错:

[[email protected] stack_trace]$ g++ -o backtrace backtrace.cpp 
backtrace.cpp:12:21: exeinfo.h: No such file or directory
backtrace.cpp: In function `void stack_info()':
backtrace.cpp:22: error: `backtrace' was not declared in this scope
backtrace.cpp:25: error: `backtrace_symbols' was not declared in this scope
backtrace.cpp:26: error: expected `;' before "for"
backtrace.cpp:26: error: `i' was not declared in this scope
backtrace.cpp:26: error: expected `;' before ')' token
backtrace.cpp: In function `void fun4(int)':
backtrace.cpp:34: error: `show_stack_info' was not declared in this scope

 源码如下:

/**
 * @file backtrace.cpp
 * @author work([email protected])
 * @date 2013/08/10 13:36:29
 * @version 1.0 
 * @brief 
 * int backtrace(void **buffer, int size);
 * char **backtrace_symbols(void* const* buffer, int size);
 * void backtrace_symbols_fd(void* const* buffer, int size, int fd); 
 **/

#include <exeinfo.h>
#include <stdio.h>
#include <stdlib.h>

#define MAX_LEN 256
void stack_info(){
    void* buffer[MAX_LEN];
    int method_num;
    char **stack_frames;
    
    method_num = backtrace(buffer, MAX_LEN);
    printf("%d method(s) returned", method_num);
    
    stack_frames = backtrace_symbols(buffer, method_num)
    for(int i=0; i<method_num; i++){
        printf("%s\n",stack_frames[i]);
    }
    free(stack_frames);
}

void fun4(int a){
    if(a>0) fun4(--a);
    else show_stack_info();
}

static void fun3(int a){
    fun4(--a);
}
void fun2(int a){
    fun3(--a);
}
void fun1(int a){
    fun2(--a);
}

int main(){
    fun1(10);
    return 0;
}
 

3. Java中运行时堆栈分析

public class StackTrace {
	static void printStackTrace() {
		Throwable th = new Throwable();
		/*
		 * An element in a stack trace, as returned by
		 * Throwable.getStackTrace(). Each element represents a single stack
		 * frame. All stack frames except for the one at the top of the stack
		 * represent a method invocation. The frame at the top of the stack
		 * represents the execution point at which the stack trace was
		 * generated. Typically, this is the point at which the throwable
		 * corresponding to the stack trace was created.
		 */
		StackTraceElement[] eles = th.getStackTrace();

		if (eles != null) {
			for (int i = 0; i < eles.length; i++) {
				System.out.println(eles[i]);

				// System.out.println("File name: " + eles[i].getFileName()
				// + ", Line number: " + eles[i].getLineNumber()
				// + ", Class name: " + eles[i].getClassName()
				// + ", Method name: " + eles[i].getMethodName());
			}
		}
	}

	static int inner() {
		printStackTrace();
		System.out.println("inner");
		return 2;
	}

	static void outer(double d) {
		System.out.println("outer: " + inner());
	}

	public static void main(String[] args) {
		outer(3.3);
	}

}

猜你喜欢

转载自chuanwang66.iteye.com/blog/1922905