broadcom6838开发环境实现函数栈追踪

在嵌入式设备开发中,内核为内核模块的函数栈追踪已经提供了很好的支持,但用户层的函数栈追踪确没有很好的提供支持。在网上收集学习函数栈跟踪大部分都是描述INTER体系架构支持栈帧的实现机制,或者直接给出glibc的现成库函数。但如果开发环境是broadcom相关方案,通常使用的是MIPS32的体系架构,并且C库使用的是更小的uclibc,虽然MIPS32体系架构中也定义了栈帧寄存器s8(类似于Inter体系架构中常见的ebp寄存器),但经过GCC编译器的优化选项控制后,通常在O1以上的优化就已经去除了s8栈帧的使用,所以给函数栈追踪的实现就带来了一点点小麻烦。

通常情况下,函数的返回值还是使用压栈实现,所以只要知道了函数每次调用时,返回值(ra寄存器)与当前栈顶(sp寄存器)的偏移量,就可以实现函数栈追踪,对函数反汇编可以了解到这个偏移量通过sw ra, xxxx(sp)可以得到,另一个难题是怎么得到函数每次调用时的当前栈顶(sp寄存器),通过函数反汇编可以了解到addiu sp, sp, xxxx指令就是每次给函数分配当前栈帧大小的,所以只要得到这个栈帧大小然后用sp进行差值计算就可以往回推出上一个sp的值了,还剩下最后一个问题,一步步获取上一个函数的栈顶,什么时候结束?答案就是只要栈顶sp的寄存器为0就追踪到头了,通过这些分析我们可以了解到当前实现机制根本没用到程序的栈段内容,对,完全从程序指令段做为线索,获取程序运行时指令一步步找出函数栈的调用,看起来很酷,但现实也比较残酷,比如我们上面分析说要得到栈顶sp的寄存器为0表示追踪到头,我当前调试环境在__start(可以参考一些链接加载的相关技术资料了解,其它main并不是c的起始函数,__start才是c语言的起始函数)函数中使得sp为0的汇编指令为addu
ra, zero, zero,我还不清楚其它编译器是否会使用其它指令方法进行设置,所以我们当前实现的这个函数栈追跟踪功能的实现代码并不是很标准的,如果读者有幸参考该代码,请一定要在理解上面描述的原理基础上,另外需要在您自己的编译开发环境进行调试,确保这个追踪功能代码自己不会引发Crash再使用,否则那玩笑就开的大了。

剩下就不多说了,直接付上我当前的开发环境及实现源码,以及最终在broadcom6838板上运行的测试程序的效果图。

开发环境:

体系结构:mips32

GCC版本:gcc-4.6

内核版本:linux-3.4

C库版本:uclibc-0.9.32

工具版本:binutils-2.21

实现源码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <ucontext.h>

#define abs(s) ((s) < 0 ? -(s) : (s))

#define CALL_TRACE_MAX_SIZE             (10)

#define MIPS_ADDIU_SP_SP_XXXX           (0x27bd0000) /* instruction code for addiu sp, sp, xxxx */
#define MIPS_SW_RA_XXXX_SP              (0xafbf0000) /* instruction code for sw ra, xxxx(sp) */
#define MIPS_ADDU_RA_ZERO_ZERO          (0x0000f821) /* instruction code for addu ra, zero, zero */

int backtrace_mips32(void **buffer, int size, ucontext_t *uc)
{
    unsigned long *addr = NULL;
    unsigned long *ra = NULL;
    unsigned long *sp = NULL;
    unsigned long *pc = NULL;
    size_t         ra_offset = 0;
    size_t         stack_size = 0;
    int            depth = 0;

    if (size == 0)
    {
       return 0;
    }

    if (buffer == NULL || size < 0 || uc == NULL)
    {
       return -1;
    }

    pc = (unsigned long *)(unsigned long)uc->uc_mcontext.pc;
    ra = (unsigned long *)(unsigned long)uc->uc_mcontext.gregs[31];
    sp = (unsigned long *)(unsigned long)uc->uc_mcontext.gregs[29]; 

    buffer[0] = pc;

    if ( size == 1 )
    {
        return 1;
    }

    for ( addr = pc; !ra_offset || !stack_size; --addr )
    {
        if ( ((*addr) & 0xffff0000) == MIPS_SW_RA_XXXX_SP)
        {
            ra_offset = (short)((*addr) & 0xffff);
        }
        else if ( ((*addr) & 0xffff0000) == MIPS_ADDIU_SP_SP_XXXX)
        {
            stack_size = abs((short)((*addr) & 0xffff));
        }
        else if ( (*addr) == MIPS_ADDU_RA_ZERO_ZERO )
        {
           return 1;
        }
    }

    if ( ra_offset > 0 )
    {
        ra = *(unsigned long *)((unsigned long)sp + ra_offset);
    }

    if ( stack_size > 0 )
    {
        sp = (unsigned long *)((unsigned long)sp + stack_size);
    }

    for (depth = 1; depth < size && ra; ++depth)
    {
       buffer[depth] = ra;

       ra_offset = 0;
       stack_size = 0;

       for (addr = ra; !ra_offset || !stack_size; --addr)
       {
            if ( ((*addr) & 0xffff0000) == MIPS_SW_RA_XXXX_SP)
            {
                ra_offset = (short)((*addr) & 0xffff);
            }
            else if ( ((*addr) & 0xffff0000) == MIPS_ADDIU_SP_SP_XXXX)
            {
                stack_size = abs((short)((*addr) & 0xffff));
            }
            else if ( (*addr) == MIPS_ADDU_RA_ZERO_ZERO )
            {
               return depth + 1;
            }
       }

       ra = *(unsigned long *)((unsigned long)sp + ra_offset);
       sp = (unsigned long *)((unsigned long)sp + stack_size);
    }

    return depth;
}

void signal_process(int sig_no, siginfo_t *sig_info, void *ucontext)
{
    int i = 0;
    unsigned long *callStack[CALL_TRACE_MAX_SIZE] = {0};

    printf("\r\n*******************************************\r\n");

    printf("recv signo %d\r\n", sig_no);

    backtrace_mips32((void **)callStack, CALL_TRACE_MAX_SIZE, (ucontext_t *)ucontext);

    printf("\r\ncall tracing:\r\n");
    for ( i = 0; i < CALL_TRACE_MAX_SIZE; i++ )
    {
        if ( callStack[i] == 0 )
        {
            break;
        }

        printf("\t[%d] addr 0x%x\r\n", i, callStack[i]);
    }

    printf("*******************************************\r\n");

    if (sig_no == SIGSEGV)
    {
       signal(sig_no, SIG_DFL);
    }

    sleep(3);
}

void c(void)
{
    int *p = 0;

    printf("I am function [c]\r\n");

    *p = 888;
}

void b(void)
{
    printf("I am function [b]\r\n");

    c();
}

void a(void)
{
    printf("I am function [a]\r\n");

    b();
}

int main(int argc, char *argv[])
{
    struct sigaction act = {0};

    act.sa_handler = signal_process;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_SIGINFO;

    sigaction(SIGSEGV, &act, NULL);

    printf("I am function [main]\r\n");

    a();

    return 0;
}

运行效果图:

broadcom6838开发环境实现函数栈追踪,布布扣,bubuko.com

时间: 2024-10-05 23:27:07

broadcom6838开发环境实现函数栈追踪的相关文章

我的全栈之路-Go语言基础之Go语言开发环境搭建

我的全栈之路-Go语言基础之Go语言开发环境搭建 我的全栈之路 2.1 Go语言编译器下载 在开发Go语言的程序之前,首先去官网 golang.google.cn下载Go语言的编译器,编译器的作用就是将开发人员使用Go语言针对Go编译器编写的Go程序编译成对应平台的机器指令.因为计算机无法识别开发人员编写的程序,需要借助Go编译器来将源程序编译成计算机识别的二进制机器指令. golang官网提供了Windows,Linux,macOS版本的Go语言编译下载 当点击Download Go后,会跳转

Vue+koa2开发一款全栈小程序(5.服务端环境搭建和项目初始化)

1.微信公众平台小程序关联腾讯云 腾讯云的开发环境是给免费的一个后台,但是只能够用于开发,如果用于生产是需要花钱的,我们先用开发环境吧 1.用小程序开发邮箱账号登录微信公众平台 2.[设置]→[开发者工具]→第一次是git管理,开启腾讯云关联 3.会一路跳转到腾讯云的[开通开发环境]的流程要走 1.已经完成 2.下载安装微信开发者工具,也已经下载安装了 3.下载Node.js版本Demo 将demo中的server文件夹,复制到mpvue项目中 在项目下的project.config.json中

我的全栈之路-C语言基础之集成开发环境搭建

我的全栈之路-C语言基础之集成开发环境搭建 我的全栈之路 2.1 C语言集成开发环境搭建 目前主流的操作系统(Windows,Linux,macOS)都有完善的C语言集成开发环境,用于编辑.编译.调试.打包部署C程序. 操作系统 开发工具 Windows10 1903 Visual Studio2019 macOS10.14 XCode10.3 Ubuntu18.04 QT5.13 Windows作为世界上最流行的桌面操作系统,当前最新版本为Windows10 1903,VisualStudio

Java从0到全栈-Java语言概述与开发环境搭建

Java从0到全栈-Java语言概述与开发环境搭建 Java从0到全栈 Java语言概述 Java发展历史 Java之父-James Golsing 起源 1991年,SUN(Standford University Network)公司的James Golsing领导的工程师小组想要开发一种用于像电视机.微波炉.电话这样的消费类电子产品的小型计算机语言,该产品的特点是由于不同的厂商选择不同的CPU和操作系统,因此要求该语言不能和特定的体系结构绑定在一起,也就是跨平台的.最初将这个语言命名为Oa

golang开发环境搭建

软件环境 以windows环境为例 1.go1.3.3.windows-amd64.msi Go语言安装包,下载地址: 官方地址:https://golang.org/dl/ Golang中国地址:http://www.golangtc.com/download 2.Git-1.9.4-preview20140929.exe git版本管理工具,golang很多第三方包被托管在github上,git结合go get可以下载对应的代码包 下载地址:http://git-scm.com/downlo

c函数调用过程原理及函数栈帧分析

转载自地址:http://blog.csdn.net/zsy2020314/article/details/9429707       今天突然想分析一下函数在相互调用过程中栈帧的变化,还是想尽量以比较清晰的思路把这一过程描述出来,关于c函数调用原理的理解是很重要的. 1.关于栈 首先必须明确一点也是非常重要的一点,栈是向下生长的,所谓向下生长是指从内存高地址->低地址的路径延伸,那么就很明显了,栈有栈底和栈顶,那么栈顶的地址要比栈底低.对x86体系的CPU而言,其中 ---> 寄存器ebp(

Linux 下函数栈帧分析

1.关于栈 对于程序,编译器会对其分配一段内存,在逻辑上可以分为代码段,数据段,堆,栈 代码段:保存程序文本,指令指针EIP就是指向代码段,可读可执行不可写 数据段:保存初始化的全局变量和静态变量,可读可写不可执行 BSS:未初始化的全局变量和静态变量 堆(Heap):动态分配内存,向地址增大的方向增长,可读可写可执行 栈(Stack):存放局部变量,函数参数,当前状态,函数调用信息等,向地址减小的方向增长,非常非常重要,可读可写可执行.如下图所示: 首先必须明确一点也是非常重要的一点,栈是向下

01_Android应用开发环境_01_android发展史及系统架构

1.1 Android发展史与现状 Andy Rubin创立22个月后→(2005年)Google收购. 2008 Patrick Brady于Google I/O 演讲"Anatomy & Physiology of an Android",并提出的 Android HAL 架构图. Android版本升级 Android系统今后将继续每半年一次的升级步伐,分别定在每年的夏天和年终.每代Android系统都将以食物命名,比如1.5版叫做 Cupcake(纸杯蛋糕),1.6版为

搭建Kafka开发环境

我们搭建了kafka的服务器,并可以使用Kafka的命令行工具创建topic,发送和接收消息.下面我们来搭建kafka的开发环境.添加依赖 搭建开发环境需要引入kafka的jar包,一种方式是将Kafka安装包中lib下的jar包加入到项目的classpath中,这种比较简单了.不过我们使用另一种更加流行的方式:使用maven管理jar包依赖.创建好maven项目后,在pom.xml中添加以下依赖: <dependency> <groupId> org.apache.kafka&l