精华内容
下载资源
问答
  • 主要介绍了JavaScript实现显示函数调用堆栈的方法,实例分析了JavaScript显示函数调用堆栈的具体作用与使用方法,需要的朋友可以参考下
  • arm与arm64调用栈

    2018-09-15 17:53:49
    ARM Procedure Call Standard定义了各寄存器在函数调用过程中的作用、基础类型的长度、以及函数调用基本准则,包括处理、参数传递等。 本文通过实例描述arm与arm64在函数调用过程中栈帧的处理方法,理解栈帧的特点...
  • 主要介绍了TypeScript之调用栈的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • 本篇文章是对c#在未出现异常情况下查看当前调用堆栈的解决方法进行了详细的分析介绍,需要的朋友参考下
  • 下面小编就为大家带来一篇浅谈在linux kernel中打印函数调用堆栈的方法。小编觉得挺不错的。现在就分享给大家。也给大家做个参考。一起跟随小编过来看看吧
  • C语言函数调用栈(一)

    2021-02-02 23:22:49
    函数调用过程通常使用堆栈实现,每个用户态进程对应一个调用栈结构(callstack)。编译器使用堆栈传递函数参数、保存返回地址、临时保存寄存器原有值(即函数调用的上下文)以备恢复以及存储本地局部变量。不同处理器和...
  • Linux下追踪函数调用堆栈 文章目录Linux下追踪函数调用堆栈0x01 backtrace函数0x02 backtrace_symbols函数0x03 backtrace_symbols_fd函数0x04 使用案例 一般察看函数运行时堆栈的方法是使用GDB之类的外部调试器,...

    Linux下追踪函数调用堆栈


    一般察看函数运行时堆栈的方法是使用 GDB之类的外部调试器,有些时候为了分析程序的 BUG,主要针对长时间运行程序的分析,在程序出错时打印出函数的调用堆栈是非常有用的。

    在头文件*“execinfo.h”*中声明了三个函数用于获取当前线程的函数调用堆栈

     #include <execinfo.h>
    
    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);
    

    0x01 backtrace函数

    Function: int backtrace(void**buffer, int size)
    该函数用于获取当前线程的调用堆栈,获取的信息将会被存放在buffer中,它是一个指针列表。
    参数buffer:是一个指针列表,保存获取的信息
    参数size: 用来指buffer中可以保存多少个void* 元素。
    函数返回值是实际获取的指针个数,最大不超过size大小
    在buffer中的指针实际是从堆栈中获取的返回地址,每一个堆栈框架有一个返回地址。
    注意: 某些编译器的优化选项对获取正确的调用堆栈有干扰, 另外内联函数没有堆栈框架; 删除框架指针也会使无法正确解析堆栈内容
    
    backtrace() returns the number of addresses returned in buffer, which is not greater than size.  If the return value is less than size, then the full backtrace was stored;  if  it
    is equal to size, then it may have been truncated, in which case the addresses of the oldest stack frames are not returned.
    

    0x02 backtrace_symbols函数

    Function: char ** backtrace_symbols (void *const *buffer, int size)
    backtrace_symbols将从backtrace函数获取的信息转化为一个字符串数组
    参数buffer:是从backtrace函数获取的数组指针,
    size是该数组中的元素个数(backtrace的返回值)    
    函数返回值是一个指向字符串数组的指针,它的大小同buffer相同
    
    On success, backtrace_symbols() returns a pointer to the array malloc(3)ed by the call; on error, NULL is returned.
    

    每个字符串包含了一个相对于buffer中对应元素的可打印信息。它包括函数名,函数的偏移地址,和实际的返回地址现在,只有使用ELF二进制格式的程序和库中才能获取函数名称和偏移地址
    在其他系统,只有十六进制的返回地址能被获取。另外,你可能需要传递相应的标志给链接器,以能支持函数名功能(比如,在使用GNU ld的系统中,你需要传递(-rdynamic))。该函数的返回值是通过malloc函数申请的空间,因此调用这必须使用free函数来释放指针
    注意:如果不能为字符串获取足够的空间函数的返回值将会为NULL

    备注:gcc -rdynamic参数

    选项 -rdynamic 用来通知链接器将所有符号添加到动态符号表中
    (目的是能够通过使用 dlopen 来实现向后跟踪)
    -rdynamic
    Pass the flag ‘-export-dynamic’ to the ELF linker, on targets that support
    it. This instructs the linker to add all symbols, not only used ones, to the
    dynamic symbol table. This option is needed for some uses of dlopen or to
    allow obtaining backtraces from within a program.

    0x03 backtrace_symbols_fd函数

    Function:void backtrace_symbols_fd (void *const *buffer, int size, int fd) 
        
    backtrace_symbols_fd() takes the same buffer and size arguments as backtrace_symbols(), but instead of returning an array of strings to the caller, it writes the strings, one per line, to the file descriptor fd. 
    backtrace_symbols_fd() does not call malloc(3), and so can be employed in situations where the latter function might fail.
    

    backtrace_symbols_fd与backtrace_symbols函数具有相同的功能,不同的是它不会给调用者返回字符串数组,而是将结果写入文件描述符为fd的文件中,每个函数对应一行。它不需要调用malloc函数,因此适用于有可能调用该函数会失败的情况。

    0x04 使用案例

    下面是glibc中的实例:

    #include <execinfo.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    void myfunc3(void)
    {
        int j, nptrs;
    #define SIZE 100
        void *buffer[100];
        char **strings;
    
        nptrs = backtrace(buffer, SIZE);
        printf("backtrace() returned %d addresses\n", nptrs);
    
        /* The call backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO)
            would produce similar output to the following: */
        strings = backtrace_symbols(buffer, nptrs);
        if (strings == NULL) {
            perror("backtrace_symbols");
            exit(EXIT_FAILURE);
        }
    
        for (j = 0; j < nptrs; j++)
            printf("%s\n", strings[j]);
    
        free(strings);
    }
    
    /* "static" means don't export the symbol... */
    static void myfunc2(void)
    {
        myfunc3();
    }
    
    void myfunc(int ncalls)
    {
        if (ncalls > 1)
            myfunc(ncalls - 1);
        else
            myfunc2();
    }
    
    int main(int argc, char *argv[])
    {
        if (argc != 2) 
        {
            fprintf(stderr, "%s num-calls\n", argv[0]);
            exit(EXIT_FAILURE);
        }
    
        myfunc(atoi(argv[1]));
        exit(EXIT_SUCCESS);
    }
    

    运行结果:

    [root@python zbb]# gcc trace.c -rdynamic -o trace
    [root@python zbb]# ./trace 3
    backtrace() returned 8 addresses
    ./trace(myfunc3+0x1f) [0x4009cc]
    ./trace() [0x400a61]
    ./trace(myfunc+0x25) [0x400a88]
    ./trace(myfunc+0x1e) [0x400a81]
    ./trace(myfunc+0x1e) [0x400a81]
    ./trace(main+0x59) [0x400ae3]
    /lib64/libc.so.6(__libc_start_main+0xf5) [0x7fde34fe63d5]
    ./trace() [0x4008e9]
    [root@python zbb]# ./trace 2
    backtrace() returned 7 addresses
    ./trace(myfunc3+0x1f) [0x4009cc]
    ./trace() [0x400a61]
    ./trace(myfunc+0x25) [0x400a88]
    ./trace(myfunc+0x1e) [0x400a81]
    ./trace(main+0x59) [0x400ae3]

    我们还可以利用backtrace来定位程序段错误位置。

    通常情况下,程序发生段错误时系统会发送SIGSEGV信号给程序,缺省处理是退出函数。我们可以使用 signal(SIGSEGV, &your_function); 函数来接管SIGSEGV信号的处理,程序在发生段错误后,自动调用我们准备好的函数,从而在那个函数里来获取当前函数调用栈。

    #include <stdio.h>
    #include <stdlib.h>
    #include <stddef.h>
    #include <execinfo.h>
    #include <signal.h>
     
    void Handler(int signo)
    {
    	void *buffer[1024] = {0};
    	size_t size;
    	char **strings = NULL;
    	size_t i = 0;
     
    	size = backtrace(buffer, 1024);
    	fprintf(stdout, "backtrace() returned %d addresses\n", size);
    	strings = backtrace_symbols(buffer, size);
    	if (strings == NULL)
    	{
    		perror("backtrace_symbols.");
    		exit(EXIT_FAILURE);
    	}
    	
    	for (i = 0; i < size; i++)
    	{
    		fprintf(stdout, "%s\n", strings[i]);
    	}
    	free(strings);
    	strings = NULL;
    	exit(0);
    }
     
    void myfunc3()
    {
    	*((volatile char *)0x0) = 0x20;
    }
     
    static void myfunc2(void)
    {
        myfunc3();
    }
    
    void myfunc(int ncalls)
    {
        if (ncalls > 1)
            myfunc(ncalls - 1);
        else
            myfunc2();
    }
    
    int main(int argc, char *argv[])
    {
        if (argc != 2) 
        {
            fprintf(stderr, "%s num-calls\n", argv[0]);
            exit(EXIT_FAILURE);
        }
        if (signal(SIGSEGV, Handler) == SIG_ERR)
    		perror("can't catch SIGSEGV");
        myfunc(atoi(argv[1]));
        exit(EXIT_SUCCESS);
    }
    
    [root@python zbb]# gcc -g testTrace.c -rdynamic -o testTrace
    [root@python zbb]# ./testTrace 2
    backtrace() returned 9 addresses
    ./testTrace(Handler+0x4f) [0x400a5c]
    /lib64/libc.so.6(+0x36280) [0x7fbe462f4280]
    ./testTrace(myfunc3+0x9) [0x400b24]
    ./testTrace() [0x400b37]
    ./testTrace(myfunc+0x25) [0x400b5e]
    ./testTrace(myfunc+0x1e) [0x400b57]
    ./testTrace(main+0x78) [0x400bd8]
    /lib64/libc.so.6(__libc_start_main+0xf5) [0x7fbe462e03d5]
    ./testTrace() [0x400949]
    [root@python zbb]# addr2line 0x400a5c -e testTrace -f
    Handler
    /home/zbb/testTrace.c:14
    [root@python zbb]# addr2line 0x400b24 -e testTrace -f
    myfunc3
    /home/zbb/testTrace.c:34
    

    Reference:
    参考链接1
    参考链接2

    展开全文
  • 接下来将通过下面几个问题解析函数调用中对堆栈理解:(1)函数调用过程中堆栈在内存中存放的结构如何?(2)汇编语言中call,ret,leave等具体操作时如何?(3)linux中任务的堆栈,数据存放是如何?1. 函数调用...

    82349e66500c5c95d8087d3a9728663e.png

    接下来将通过下面几个问题解析函数调用中对堆栈理解:

    (1)函数调用过程中堆栈在内存中存放的结构如何?

    (2)汇编语言中call,ret,leave等具体操作时如何?

    (3)linux中任务的堆栈,数据存放是如何?

    1. 函数调用过程中堆栈在内存中存放的结构如何?

    计算机,嵌入式设备,智能设备等其实都是有软件和硬件两部分组成,具体实现也许复杂,但整体的结构也就如此。软件运行在硬件上,告诉硬件该干什么。操作系统软件是在启动过程中经过BIOS,bootloarder等(如果有这些过程的话)从磁盘加载到内存中,而自定义软件则是编写存放到磁盘中,只有通过加载才会到内存中运行。

    首先我们来看一下什么是堆、栈还有堆栈,我们经常说堆栈其实它是等同于栈的概念。

    可以通俗意义上这样理解堆,堆是一段非常大的内存空间,供不同的程序员从其中取出一段供自己使用,使用之后要由程序员自己释放,如果不释放的话,这部分存储空间将不能被其他程序使用。堆的存储空间是不连续的,因为会因为不同时间,不同大小的堆空间的申请导致其不连续性。堆的生长是从低地址向高地址增长的。

    对栈的理解是,栈是一段存储空间,供系统或者操作系统使用,对程序员来说一般是不可见的,除非从一开始由程序员自己通过汇编等自己构建栈,栈会由系统管理单元自己申请释放。栈是从高地址向低地址生长的,既栈底在高地址,栈顶低地址。

    其次我们看一下应用程序的加载,应用程序被加载进内存后,由操作系统为其分配堆栈,程序的入口函数会是main函数。不过main函数也不是第一个被调用的函数,我们通过简单的例子讲解。

    #include
    #include string.h>
    int function(int arg)
    {
    return arg;
    }
    int main(void)
    {
    int i = 10;
    int j;
    j = function(i);
    printf("%dn",j);
    return 0;
    }

    用gcc -S main.c 生成汇编文件main.s, 其中function的汇编代码如下:

    function:
    .LFB0:
    .cfi_startproc
    pushq %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq %rsp, %rbp
    .cfi_def_cfa_register 6
    movl %edi, -4(%rbp)
    movl -4(%rbp), %eax
    popq %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc

    看以看到当函数被调用时,首先会把调用函数的栈底压栈到自己函数的栈中(pushq %rbp),然后将原来函数栈顶rsp作为当前函数的栈底(movq %rsp, %rbp)。函数运行完成时,会将压入栈中的rbp重新出栈到rbp中(popq %rbp)。当前function汇编函数没有显示出栈顶的变化(rsp的变化),我们可以通过main函数来看栈顶的变化,汇编代码如下:

    main:
    .LFB1:
    .cfi_startproc
    pushq %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq %rsp, %rbp
    .cfi_def_cfa_register 6
    subq $16, %rsp
    movl $10, -4(%rbp)
    movl -4(%rbp), %eax
    movl %eax, %edi
    call function
    movl %eax, -8(%rbp)
    movl -8(%rbp), %eax
    movl %eax, %esi
    movl $.LC0, %edi
    movl $0, %eax
    call printf
    movl $0, %eax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc

    从上面的汇编代码可以看到首先也是压栈和设置新栈底的过程,从此可以看出main函数也是被调用的函数,而不是第一个调用函数。代码中的黄色部分是当前栈顶变化,从使用的subq可以知道,栈顶的地址要小于栈底的地址,所以栈是从高地址向低地址生长。

    接下来可能有点绕,慢慢读,将用语言描述函数调用过程,调用函数会将被调用函数的实参从右往左的顺序压入调用函数的栈中,通过call指令调用被调用函数,首先将return address(也就是call指令的后一条指令的地址)压入调用函数栈中,这时rsp寄存器中存储的地址是存放return address内存地址的下一地址值,这时调用函数的栈结构形成,然后就会进入被调用函数的作用域中。被调用函数首先将调用函数的rbp压入被调用函数栈中(其实这个地址就是rsp寄存器中存储的地址),接下来将会将这个地址作为被调用函数的rbp地址,才会有movq %rsp, %rbp指令设置被调用函数的栈底。如上所描述的构成了函数调用的堆栈结构如下图所示。

    c4cb5b6f5076747906289051e7abb68a.png

    2. 汇编语言中call,ret,leave等具体操作时如何?

    push:将数据压入栈中,具体操作是rsp先减,然后将数据压入sp所指的内存地址中。rsp寄存器总是指向栈顶,但不是空单元。

    pop:将数据从栈中弹出,然后rsp加操作,确保rsp寄存器指向栈顶,不是空单元。

    call:将下一条指令的地址压入当前调用函数的栈中(将PC指令压入栈中,因为在从内存中取出call指令时,PC指令已经自动增加),然后改变PC指令的为call的function的地址,程序指针跳转到新function。

    ret:当指令指到ret指令行时,说明一个函数已经结束了,这时候rsp已经从被调用函数的栈指到了调用函数构建的返回地址位置。ret是将rsp所指栈顶地址中的内容赋值给PC,接下来将执行call function的下一条指令。

    leave:相当于mov %esp, %ebp, pop ebp。头一条指令其实是把ebp所指的被调用函数的栈底作为新的栈顶,pop指令时相当于把被调用函数的栈底弹出,rsp指向返回地址。

    int:通过其后加中断号,实现软件引发中断,linux操作系统中系统调用多有此实现,其他实时操作系统中在操作系统移植时,会有tick心脏函数也有此实现。

    其他的汇编指令在此就不多讲了,因为汇编指令众多,硬件cpu寄存器也因硬件不同而不同,此节就讲了函数构建进入和离开函数时用到的几个汇编指令,这几条指令和栈变化有关。自己构建汇编函数,或者是在读linux操作系统的系统调用时会对其理解有帮助。硬件寄存器中rsp,和rbp用于指示栈顶和栈底。

    3. linux中任务的堆栈,数据存放是如何?

    linux的任务堆栈分为两种:内核态堆栈和用户态堆栈。接下来简单介绍一下这两个堆栈,如果以后有机会将详细介绍这两个堆栈。

    1. 内核态堆栈

    linux操作系统分为内核态和用户态。用户态代码访问代码和数据收到诸多限制,用户态主要是为程序员编写程序使用,处于用户态的代码不可以随便访问linux内核态的数据,这主要就是设置用户态的权限,安全考虑。但是用户态可以通过系统调用接口,中断,异常等访问指定内核态的内容。内核态主要是用于操作系统内核运行以及管理,可以无限制的访问内存地址和数据,权限比较大。

    linux操作系统的进程是动态的,有生命周期,进程的运行和普通的程序运行一样,需要堆栈的帮助,如果在内核存储区域内为其提前分配堆栈的话,既浪费内核内存(任务地址大约3G的空间),也不能灵活的构建任务,所以linux操作系统在创建新的任务时,为其分配了8k的存储区域用于存放进程内核态的堆栈和线程描述符。线程描述符位于分配的存储区域的低地址区域,大小固定,而内核态堆栈则从存储区域的高地址开始向低地址延伸。如果之前版本为内核态堆栈和线程描述符分配4k的存储空间时,则需要为中断和异常分配额外的栈供其使用,防止任务堆栈溢出。

    78b611e2e9eddcc181736b830d284f36.png

    2. 用户态堆栈

    对于32位的linux操作系统,每个任务都会有4G的寻址空间,其中0-3G为用户寻址空间,3G-4G为内核寻址空间。每个任务的创建都会有0-3G的用户寻址空间,但是3G-4G的内核寻址空间是属于所有任务共享的。这些地址都属于线性地址,需要通过地址映射转换成物理地址。为了实现每个任务在访问0-3G的用户空间时不至于混淆地址,每个任务的内存管理单元都会有一个属于自身的页目录pgd,在任务创建之初会创建新的pgd,任务会通过地址映射为0-3G空间映射物理地址。用户态的堆栈就在这0-3G的用户寻址空间中分配,和之前的main函数以及function函数构建堆栈一样,但是具体映射到哪个物理地址,还需要内存管理单元去做映射操作。总之,linux任务用户态的堆栈和普通应用程序一样,由操作系统分配和释放,对程序员来说不可见,不过因为操作系统的原因,任务用户程序寻址有限制。

    展开全文
  • cpp打印调用堆栈

    2019-04-04 01:17:56
    NULL 博文链接:https://heiheisoftware.iteye.com/blog/1483320
  • 转载自:...但是并没有相关API支持获取任意线程的调用栈,所以只能自己编码实现。1. 基础结构一个线程的调用栈是什么样的呢?我的理解是应该包含当前线程的执行地址,并且从这个地址可以一级一...

    转载自:http://blog.csdn.net/jasonblog/article/details/49909163

    如果要获取当前线程的调用栈,可以直接使用现有API:[NSThread callStackSymbols]

    但是并没有相关API支持获取任意线程的调用栈,所以只能自己编码实现。

    1. 基础结构

    一个线程的调用栈是什么样的呢?

    我的理解是应该包含当前线程的执行地址,并且从这个地址可以一级一级回溯到线程的入口地址,这样就反向构成了一条链:线程入口执行某个方法,然后逐级嵌套调用到当前现场。

    Call_stack_layout_svg(图片来源于维基百科)

    如图所示,每一级的方法调用,都对应了一张活动记录,也称为活动帧。也就是说,调用栈是由一张张帧结构组成的,可以称之为栈帧。

    我们可以看到,一张栈帧结构中包含着Return Address,也就是当前活动记录执行结束后要返回的地址(展开)。

    那么,在我们获取到栈帧后,就可以通过返回地址来进行回溯了。

    2. 指令指针和基址指针

    我们明确了两个目标:(1)当前执行的指令,(2)当前栈帧结构。

    以x86为例,寄存器用途如下:

    SP/ESP/RSP: Stack pointer for top address of the stack.
    BP/EBP/RBP: Stack base pointer for holding the address of the current stack frame.
    IP/EIP/RIP: Instruction pointer. Holds the program counter, the current instruction address.

    可以看到,我们可以通过指令指针来获取当前指令地址,以及通过栈基址指针获取当前栈帧地址。

    那么问题来了,我们怎么获取到相关寄存器呢?

    3. 线程执行状态

    考虑到一个线程被挂起时,后续继续执行需要恢复现场,所以在挂起时相关现场需要被保存起来,比如当前执行到哪条指令了。

    那么就要有相关的结构体来为线程保存运行时的状态,经过一番查阅,得到如下信息:

    The function thread_get_state returns the execution state (e.g. the machine registers) of target_thread as specified by flavor.

    Function - Return the execution state for a thread.
    
    SYNOPSIS
    
    kern_return_t   thread_get_state
                    (thread_act_t                     target_thread,
                     thread_state_flavor_t                   flavor,
                     thread_state_t                       old_state,
                     mach_msg_type_number_t         old_state_count);
    /*
     * THREAD_STATE_FLAVOR_LIST 0
     *  these are the supported flavors
     */
    #define x86_THREAD_STATE32      1
    #define x86_FLOAT_STATE32       2
    #define x86_EXCEPTION_STATE32       3
    #define x86_THREAD_STATE64      4
    #define x86_FLOAT_STATE64       5
    #define x86_EXCEPTION_STATE64       6
    #define x86_THREAD_STATE        7
    #define x86_FLOAT_STATE         8
    #define x86_EXCEPTION_STATE     9
    #define x86_DEBUG_STATE32       10
    #define x86_DEBUG_STATE64       11
    #define x86_DEBUG_STATE         12
    #define THREAD_STATE_NONE       13
    /* 14 and 15 are used for the internal x86_SAVED_STATE flavours */
    #define x86_AVX_STATE32         16
    #define x86_AVX_STATE64         17
    #define x86_AVX_STATE           18

    所以我们可以通过这个API搭配相关参数来获得想要的寄存器信息:

    bool jdy_fillThreadStateIntoMachineContext(thread_t thread, _STRUCT_MCONTEXT *machineContext) {
        mach_msg_type_number_t state_count = x86_THREAD_STATE64_COUNT;
        kern_return_t kr = thread_get_state(thread, x86_THREAD_STATE64, (thread_state_t)&machineContext->__ss, &state_count);
        return (kr == KERN_SUCCESS);
    }

    这里引入了一个结构体叫_STRUCT_MCONTEXT

    4. 不同平台的寄存器

    _STRUCT_MCONTEXT在不同平台上的结构不同:

    x86_64,如iPhone 6模拟器:

    _STRUCT_MCONTEXT64
    {
        _STRUCT_X86_EXCEPTION_STATE64   __es;
        _STRUCT_X86_THREAD_STATE64  __ss;
        _STRUCT_X86_FLOAT_STATE64   __fs;
    };
    
    _STRUCT_X86_THREAD_STATE64
    {
        __uint64_t  __rax;
        __uint64_t  __rbx;
        __uint64_t  __rcx;
        __uint64_t  __rdx;
        __uint64_t  __rdi;
        __uint64_t  __rsi;
        __uint64_t  __rbp;
        __uint64_t  __rsp;
        __uint64_t  __r8;
        __uint64_t  __r9;
        __uint64_t  __r10;
        __uint64_t  __r11;
        __uint64_t  __r12;
        __uint64_t  __r13;
        __uint64_t  __r14;
        __uint64_t  __r15;
        __uint64_t  __rip;
        __uint64_t  __rflags;
        __uint64_t  __cs;
        __uint64_t  __fs;
        __uint64_t  __gs;
    };

    x86_32,如iPhone 4s模拟器:

    _STRUCT_MCONTEXT32
    {
        _STRUCT_X86_EXCEPTION_STATE32   __es;
        _STRUCT_X86_THREAD_STATE32  __ss;
        _STRUCT_X86_FLOAT_STATE32   __fs;
    };
    
    _STRUCT_X86_THREAD_STATE32
    {
        unsigned int    __eax;
        unsigned int    __ebx;
        unsigned int    __ecx;
        unsigned int    __edx;
        unsigned int    __edi;
        unsigned int    __esi;
        unsigned int    __ebp;
        unsigned int    __esp;
        unsigned int    __ss;
        unsigned int    __eflags;
        unsigned int    __eip;
        unsigned int    __cs;
        unsigned int    __ds;
        unsigned int    __es;
        unsigned int    __fs;
        unsigned int    __gs;
    };

    ARM64,如iPhone 5s:

    _STRUCT_MCONTEXT64
    {
        _STRUCT_ARM_EXCEPTION_STATE64   __es;
        _STRUCT_ARM_THREAD_STATE64  __ss;
        _STRUCT_ARM_NEON_STATE64    __ns;
    };
    
    _STRUCT_ARM_THREAD_STATE64
    {
        __uint64_t    __x[29];  /* General purpose registers x0-x28 */
        __uint64_t    __fp;     /* Frame pointer x29 */
        __uint64_t    __lr;     /* Link register x30 */
        __uint64_t    __sp;     /* Stack pointer x31 */
        __uint64_t    __pc;     /* Program counter */
        __uint32_t    __cpsr;   /* Current program status register */
        __uint32_t    __pad;    /* Same size for 32-bit or 64-bit clients */
    };

    ARMv7/v6,如iPhone 4s:

    _STRUCT_MCONTEXT32
    {
        _STRUCT_ARM_EXCEPTION_STATE __es;
        _STRUCT_ARM_THREAD_STATE    __ss;
        _STRUCT_ARM_VFP_STATE       __fs;
    };
    
    _STRUCT_ARM_THREAD_STATE
    {
        __uint32_t  __r[13];    /* General purpose register r0-r12 */
        __uint32_t  __sp;       /* Stack pointer r13 */
        __uint32_t  __lr;       /* Link register r14 */
        __uint32_t  __pc;       /* Program counter r15 */
        __uint32_t  __cpsr;     /* Current program status register */
    };

    通过了解以上不同平台的寄存器结构,我们可以编写出比较通用的回溯功能。

    5. 算法实现

    /**
     * 关于栈帧的布局可以参考:
     * https://en.wikipedia.org/wiki/Call_stack
     * http://www.cs.cornell.edu/courses/cs412/2008sp/lectures/lec20.pdf
     * http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64/
     */
    typedef struct JDYStackFrame {
        const struct JDYStackFrame* const previous;
        const uintptr_t returnAddress;
    } JDYStackFrame;
    
    //
    
    int jdy_backtraceThread(thread_t thread, uintptr_t *backtraceBuffer, int limit) {
        if (limit <= 0) return 0;
    
        _STRUCT_MCONTEXT mcontext;
        if (!jdy_fillThreadStateIntoMachineContext(thread, &mcontext)) {
            return 0;
        }
    
        int i = 0;
        uintptr_t pc = jdy_programCounterOfMachineContext(&mcontext);
        backtraceBuffer[i++] = pc;
        if (i == limit) return i;
    
        uintptr_t lr = jdy_linkRegisterOfMachineContext(&mcontext);
        if (lr != 0) {
            /* 由于lr保存的也是返回地址,所以在lr有效时,应该会产生重复的地址项 */
            backtraceBuffer[i++] = lr;
            if (i == limit) return i;
        }
    
        JDYStackFrame frame = {0};
        uintptr_t fp = jdy_framePointerOfMachineContext(&mcontext);
        if (fp == 0 || jdy_copyMemory((void *)fp, &frame, sizeof(frame)) != KERN_SUCCESS) {
            return i;
        }
    
        while (i < limit) {
            backtraceBuffer[i++] = frame.returnAddress;
            if (frame.returnAddress == 0
                || frame.previous == NULL
                || jdy_copyMemory((void *)frame.previous, &frame, sizeof(frame)) != KERN_SUCCESS) {
                break;
            }
        }
    
        return i;
    }
    展开全文
  • C++ 获取函数调用堆栈的 高效实现代码
  • frida 打印调用栈

    2021-02-19 09:02:29
    智姐姐的打印堆栈的脚本特别好用: console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));

    智姐姐的打印堆栈的脚本特别好用:

    console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));

    展开全文
  • 打印函数调用堆栈

    2020-07-15 10:54:33
    今天难得清闲,借此机会记录一下前段时间搞的一个小功能,打印函数的调用堆栈。 什么是函数的调用堆栈?给你看一段代码你马上就明白了。 比如我在DecideAllTaskID中调用A,那么打log之后我们就可以看到LogBacktrace...
  • JS调用堆栈可视化JS调用栈可视化工具 安装 安装babel-plugin-hound-trace 因为hound-trace实现原理是在函数中注入代码(无变量接收的函数表达式,匿名函数已过滤),所以需要配套安装此插件: yarn babel-plugin-...
  • 获取函数调用堆栈

    2020-02-05 11:35:53
    该函数用于获取当前线程的调用堆栈,获取的信息将会被存放在buffer中,它是一个指针列表。参数 size 用来指定buffer中可以保存多少个void* 元素。函数返回值是实际获取的指针个数,最大不超过size大小。 在buffer中的...
  • 最大调用堆栈大小超出错误

    千次阅读 2020-01-24 11:34:11
    我正在使用Direct Web Remoting(DWR)JavaScript库文件,并且仅在Safari(台式机和iPad)中出现错误 它说 超出最大呼叫堆栈大小。 该错
  • C/C++中手动获取调用堆栈
  • VC中如何打印当前调用堆栈
  • python查看函数调用栈

    千次阅读 2019-03-04 18:35:50
    我在看开源框架代码的时候,有时候好几天...这个时候就需要记录一下函数调用栈。 源码如下 def Caller(func): def f(*args,**kwargs): import sys from oslo_log import log as logging LOG = logging.getLog...
  • java 打印调用堆栈的2种方法:

    千次阅读 2020-02-24 21:12:08
    java 打印调用堆栈的2种方法: RuntimeException Thread.currentThread().getStackTrace() package ziegler.learn.debug; import java.util.stream.Stream; public class RunTest { void ...
  • 函数调用堆栈的过程

    万次阅读 多人点赞 2018-05-31 11:46:43
    本篇来分析函数调用的过程:通过下面一个简单的例子来进入话题:#include&lt;stdio.h&gt; int sum(int a,int b) { int tmp=0; tmp=a+b; return tmp; } int main() { int a=10; int b=20; int ret=0; ...
  • 从任何地方调用它以获取函数调用堆栈并查看您所在的当前函数是从哪里调用的 Mage :: helper ( 'stack/callstack' )-> toFirePhp (); Mage :: helper ( 'stack/callstack' )-> toLog (); 示例输出 .../app/code/...
  • android原代码中提供了打印堆栈的接口,具体调用如下: (1)包含头文件 #include <utils/CallStack.h> //该头文件位于/system/core/utils中 (2)在需要打印堆栈的位置如下调用 android:CallStack stack; stack....
  • java 获取当前方法的调用栈

    万次阅读 2019-05-17 11:38:51
    StackTrace(堆栈轨迹)存放的就是方法调用栈的信息,每次调用一个方法会产生一个方法栈,当前方法调用另外一个方法时会使用栈将当前方法的现场信息保存在此方法栈当中,获取这个栈就可以得到方法调用的详细过程。...
  • 在解bug或者排查问题时,经常需要知道一个方法在运行到里面的时候,是由谁来调用的,调用路径是什么(调用堆栈) Android 原生 SDK提供了方法: //获取调用堆栈的方法 String stackTraceString = Log....
  • 函数调用堆栈追踪(1)

    千次阅读 2018-02-21 19:39:38
    对于一个比较大的工程,函数的调用关系错综复杂,这时候仅仅靠IDE静态查看函数的调用关系已经远远不够,所以为了更好地理解代码,还是有必要去掌握一些方法和工具来跟踪函数的调用堆栈。 一、 设断点 在eclipse里...
  • VS调用堆栈窗口

    2021-04-06 15:22:29
    启动项目后,同时按下 crtl+ alt+c,完成调用堆栈窗口调出。
  • 深入浅出Android NDK之打印调用堆栈

    千次阅读 2020-03-27 19:15:04
    为了能在native层打印函数的调用堆栈,找了好久的资料,最后终于找到一个靠谱的链接: https://www.jianshu.com/p/4a5eeeee6d29 主要通过调用_Unwind_Backtrace函数来获得函数的调用堆栈,但是原文的并不好用,地址...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 884,453
精华内容 353,781
关键字:

调用栈