`

观察运行时堆栈【原创】

 
阅读更多

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

      

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

#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可以观察到如下信息

[work@cq01-testing-sdcads-vir43.vm.baidu.com 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 ()
[work@cq01-testing-sdcads-vir43.vm.baidu.com 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>(今天时间太紧,没空深究),否则报错:

[work@cq01-testing-sdcads-vir43.vm.baidu.com 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(work@baidu.com)
 * @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);
	}

}

 

 

 

 

分享到:
评论

相关推荐

    C通过运行时堆栈支持递归函数的实现。递归函数就是直接或间接调用自身的函数。

    C通过运行时堆栈支持递归函数的实现。递归函数就是直接或间接调用自身的函数。 许多教科书都把计算机阶乘和菲波那契数列用来说明递归,非常不幸我们可爱的著名的老潭老师的《C语言程序设计》一书中就是从阶乘的计算...

    STM32 堆栈检测示例

    堆栈检测在STM32开发中是一项重要的任务,它能够帮助开发者监控程序运行时的堆栈使用情况,防止堆栈溢出,从而提高系统的稳定性和安全性。 堆栈是MCU内存中的一个区域,用于存储函数调用时的返回地址、局部变量和...

    MCS51单片机程序设计时堆栈的计算方法解析

    正确地计算和管理堆栈空间,不仅可以避免程序运行中的数据错误和溢出问题,还可以提高程序的执行效率和稳定性。掌握Cx51程序设计时堆栈的计算方法,对于进行MCS51单片机开发的程序员来说是十分必要的。

    labview堆栈实现及堆栈状态机

    堆栈在实现状态机时尤其有用,因为它可以方便地管理和处理程序的不同状态。 标题“labview堆栈实现及堆栈状态机”指的是使用LabVIEW来构建和操作堆栈,并利用堆栈的概念来设计和实现一个状态机。状态机是一种用于...

    uCOS任务堆栈的深入分析

    当任务挂起时,需要把任务的运行现场放到堆栈里保护起来,TPrint 任务再次运行时再把这个现场还原,任务就能从上次断点处紧接着运行。这个现场是什么呢?从本质上讲,TPrint 任务的运行过程就是 CPU 在执行一段特定...

    易语言申请进程堆栈内存

    - **堆栈内存**:是进程内存的一部分,用于存放函数调用时的局部变量、函数参数、返回地址等,遵循“后进先出”(LIFO)的原则。 2. **申请进程堆栈内存**: - 在易语言中,申请堆栈内存意味着在当前进程的堆栈上...

    dongzuoji.zip_labview做堆栈_labview有堆栈吗

    这个VI可能包含了用户界面,允许用户直观地观察堆栈操作的过程,以及一些内部的子VI,分别实现了压栈、弹栈和其他堆栈操作。 在学习和使用这个源代码时,你可以了解以下知识点: 1. LabVIEW的基础知识,包括数据...

    ST32堆栈测试代码

    通过ST32堆栈测试代码,开发者可以确保他们的程序在特定硬件上正确运行,避免因堆栈溢出或数据字节序不匹配等问题导致的运行错误。在实际应用中,这样的测试代码也常常被用作固件开发的初始化部分,以确保系统的正确...

    vC 堆栈 异常处理

    总的来说,"vC 堆栈 异常处理"涉及的是C++编程中如何有效地利用堆栈进行函数调用,以及如何通过异常处理机制来应对运行时错误。理解和熟练掌握这些知识,将有助于编写更加健壮和可靠的vC++应用程序。在实际开发中,...

    gdb调试信息堆栈信息

    GDB是一款开源的、跨平台的调试器,它允许程序员在程序运行时检查其内部状态,包括变量值、内存空间、调用堆栈等。通过GDB,开发者可以设置断点、单步执行代码、查看变量、分析内存以及跟踪系统调用等,从而有效定位...

    IAR编译器堆栈溢出问题查找

    堆栈溢出问题在嵌入式开发中是一个常见的问题,特别是在使用IAR编译器进行STM32或LPC系列微控制器编程时。堆栈溢出通常发生在程序中分配的堆栈空间不足以存储所有局部变量、函数参数和返回地址时。这可能导致数据...

    数据结构-顺序堆栈

    泛型编程允许我们编写不依赖特定数据类型的代码,通过在运行时指定数据类型,使得同一段代码可以处理多种数据。在C语言中,由于缺乏内置的泛型支持,我们可以通过void指针和类型转换来模拟泛型堆栈。用户在使用堆栈...

    C51堆栈构成与空间需求分析

    本文将详细介绍C51堆栈的构成,以及如何进行空间需求分析,以保证系统运行时不会出现堆栈溢出等潜在错误。 在描述中重复强调了堆栈构成和空间需求分析的重要性,同时提及了“模拟栈”(或称为软件堆栈)的概念。C51...

    C++高效获取函数调用堆栈

    该方法使用程序运行堆栈回溯来确定函数的调用地址,并根据 VC 编译出来的 map 文件进行定位函数地址。该方法功能单一,使用简单,效率较高。 首先,需要获取当前函数的调用堆栈信息,然后根据堆栈信息来确定函数的...

    堆栈操作_StackSamplt

    例如,你可以尝试模拟不同的操作,观察其对堆栈状态的影响,或者编写一个简单的计算器,使用堆栈来处理表达式。 总之,堆栈作为一种基础数据结构,对于理解和编写高效程序至关重要。无论你是初学者还是经验丰富的...

    践踏堆栈攻防总结

    当程序在处理动态分配的堆栈空间时,由于没有适当的边界检查,可能导致缓冲区溢出,使得攻击者能够修改堆栈上的关键数据,比如函数返回地址,从而控制程序的执行流程。这种攻击手段通常被称为栈溢出,其变体包括堆栈...

    图片堆栈_matlab处理视频_视频_图片堆栈软件_图片堆栈_

    一个典型的例子是生物医学成像,如细胞分裂的动态观察。可以拍摄一系列显微镜图像,然后在MATLAB中将它们堆栈起来,通过分析细胞数量的变化,可以研究细胞的增殖速率。 总之,MATLAB作为一个功能强大的编程环境,...

    VC中打印当前调用堆栈信息实例

    调用堆栈,也称为运行时堆栈或函数调用栈,是程序执行过程中存储函数调用信息的数据结构。每次函数调用都会在堆栈上分配空间,保存返回地址、参数和局部变量等信息。当函数返回时,这些信息会被释放,以便为新的函数...

    IBM java线程堆栈分析工具

    线程堆栈反映了程序运行时的线程状态,帮助开发者定位和解决多线程问题,如死锁、资源竞争等。IBM为WebSphere Application Server (WAS) 提供了一款名为"jca467.jar"的线程堆栈分析工具,专门用于处理与IBM Java相关...

    linux 堆栈溢出的问题

    这种漏洞允许攻击者通过向程序的堆栈区域写入超出预期的数据量来破坏程序的正常运行,从而可能获取更高的权限,如root权限。在远程攻击中,黑客可以通过针对守护进程(daemon)的堆栈溢出来实现远程控制目标系统。...

Global site tag (gtag.js) - Google Analytics