• 函数寄存器基于MIPS架构的BackTrace实现


    最近一直在查找函数寄存器之类的问题,今天正好有机会和大家讨论一下.

        基于MIPS架构的BackTrace实现

        基础知识分析:

         

        1.         MIPS32的内部寄存器。

        最简略的方法就是通过GDB的命令,可以获得下面的列表

        

    (gdb) info registers

              zero       at       v0       v1       a0       a1       a2       a3

    R0   00000000 00000001 0000000f 0000000f 00000000 0000000f 0000000e 00000071

                t0       t1       t2       t3       t4       t5       t6       t7

    R8   00000072 00000001 00000203 80003cb1 80003cb0 0000007f 00000080 00000008

                s0       s1       s2       s3       s4       s5       s6       s7

    R16  00000001 80003bb0 00000000 00000000 00000000 00000000 00000000 00000000

                t8       t9       k0       k1       gp       sp       s8       ra

    R24  0000101a 0000000d 00000000 00000000 8000bbd0 807fffb8 00000000 80000830

                sr       lo       hi      bad    cause       pc

          00000000 00000000 00000007 00000000 00000000 80000830

               fsr      fir

          00000000 00000000

        除了32个通用寄存器以及别名外,还有8个专用寄存器,分别是:

        sr ( 全称Status ,CP0 Reg12) Processor status and control; interrupt control; andshadow set control

        lo ( 全称WatchLo , CP0 Reg18) Low-order watchpoint address

        hi ( 全称WatchHi, CP0 Reg19) High-order watchpoint address

        bad ( 全称BadVAddr, CP0 Reg8) Reports the address for the most recent address-relatedexception

        cause (CP0 Reg13) Cause of last exception

        pc 很明显这个是程序计数寄存器,奇怪的是在32个通用寄存器以及CP0的32个寄存器中都没有找到他,最接近的一个是CP0Reg14 EPC (Program counter at lastexception.)

        fsr 浮点相干寄存器,具体用途不明

        fir浮点相干寄存器,具体用途不明

         

        下表描述32个通用寄存器的别名和用途

        

    ;REGISTER

    NAME

    USAGE

    $0

    $zero

    常量0(constant value 0)

    $1

    $at

    保留给汇编器(Reserved for assembler)

    $2-$3

    $v0-$v1

    函数调用返回值(values for results and expression evaluation)

    $4-$7

    $a0-$a3

    函数调用参数(arguments)

    $8-$15

    $t0-$t7

    暂时的(或随意用的)

    $16-$23

    $s0-$s7

    保存的(或如果用,需要SAVE/RESTORE的)(saved)

    $24-$25

    $t8-$t9

    暂时的(或随意用的)

    $28

    $gp

    全局指针(Global Pointer)

    $29

    $sp

    堆栈指针(Stack Pointer)

    $30

    $fp

    帧指针(Frame Pointer)

    $31

    $ra

    返回地址(return address)

         

        Table: MIPS registersand the convention governing their use.

        

    Register Name

    Number

    Usage

    zero

    0

    Constant 0

    at

    1

    Reserved for assembler

    v0

    2

    Expression evaluation and results of a function

    v1

    3

    Expression evaluation and results of a function

    a0

    4

    Argument 1

    a1

    5

    Argument 2

    a2

    6

    Argument 3

    a3

    7

    Argument 4

    t0

    8

    Temporary (not preserved across call)

    t1

    9

    Temporary (not preserved across call)

    t2

    10

    Temporary (not preserved across call)

    t3

    11

    Temporary (not preserved across call)

    t4

    12

    Temporary (not preserved across call)

    t5

    13

    Temporary (not preserved across call)

    t6

    14

    Temporary (not preserved across call)

    t7

    15

    Temporary (not preserved across call)

    s0

    16

    Saved temporary (preserved across call)

    s1

    17

    Saved temporary (preserved across call)

    s2

    18

    Saved temporary (preserved across call)

    s3

    19

    Saved temporary (preserved across call)

    s4

    20

    Saved temporary (preserved across call)

    s5

    21

    Saved temporary (preserved across call)

    s6

    22

    Saved temporary (preserved across call)

    s7

    23

    Saved temporary (preserved across call)

    t8

    24

    Temporary (not preserved across call)

    t9

    25

    Temporary (not preserved across call)

    k0

    26

    Reserved for OS kernel

    k1

    27

    Reserved for OS kernel

    gp

    28

    Pointer to global area

    sp

    29

    Stack pointer

    fp or s8

    30

    Frame pointer

    ra

    31

    Return address (used by function call)

         

         

        2.      基于Linux的环境,应用程序可以通过抛出信号的方法挂起当前的任务,操作系统会将该任务控制块信息(TCB)交由应用程序注册的信号处置函数来处置,该信息中包括了

        下面这个信号量上下文结构体,里面含有我们需要的CPU寄存器信息。

        linux/2.4.20/include/asm-mips/sigcontext.h

        

    /*

    * Keep this struct definition in sync with the sigcontext fragment

    * in arch/mips/tools/offset.c

    */

    struct sigcontext {

            unsigned int       sc_regmask;          /* Unused */

            unsigned int       sc_status;

            unsigned long long sc_pc;

            unsigned long long sc_regs[32];

            unsigned long long sc_fpregs[32];

            unsigned int       sc_ownedfp;          /* Unused */

            unsigned int       sc_fpc_csr;

            unsigned int       sc_fpc_eir;          /* Unused */

            unsigned int       sc_used_math;

            unsigned int       sc_ssflags;          /* Unused */

            unsigned long long sc_mdhi;

            unsigned long long sc_mdlo;

     

            unsigned int       sc_cause;            /* Unused */

            unsigned int       sc_badvaddr;         /* Unused */

     

            unsigned long      sc_sigset[4];        /* kernel's sigset_t */

    };

         

        3.       mips32常用汇编指令描述

        源代码:

        

    #include <stdio.h>

    #include <stdlib.h>

     

    int func_b(int a)

    {

        return 0;

    }

     

    int func_a(int a)

    {

        func_b(0);

        return 0;

    }

     

    int main(int argc, char* argv[])

    {

        int temp = 0;

        func_a(temp);

        return 0;

    }

         

        下面是将-O2编译出的elf反编译后func_a的汇编指令:

        

    -   0x80000810 <func_a>:         lui a0,0x8000  /* a0 = 0x80000000 */

    -   0x80000814 <func_a+4>:       lui a1,0x8000 /* a1 = 0x80000000 */

    -   0x80000818 <func_a+8>:       addiu  sp,sp,-24

    /* sp = sp - 24 = 0x807fffd0 - 24 = 0x807FFFB8 */

    -   0x8000081c <func_a+12>:      addiu  a0,a0,12876

    /* a0 = a0 + 12876 = 0x8000324C */

    -   0x80000820 <func_a+16>:      addiu  a1,a1,12888

    /* a1 = a1 + 12888 */

    -   0x80000824 <func_a+20>:      sw  ra,16(sp)

    /* SW Store Word Mem[Rs+offset] = Rt

    ra = 0x80000858

    sp = 0x807FFFB8

    功能相当于下面的C代码

    *(unsigned int*)(sp + 16) = ra */

    -   0x80000828 <func_a+24>:      jal 0x80000dd0 <printf>

    /* JAL Jump and Link GPR[31] = PC + 8

    PC = PC[31:28] || offset<<2

    ra = pc+8 = 0x80000828 + 8 = 0x80000830

    pc = 0x80000dd0 */

        0x8000082c <func_a+28>:      li  a2,78

    -   0x80000830 <func_a+32>:      jal 0x800007e0 <func_b>

        0x80000834 <func_a+36>:      move   a0,zero

    -   0x80000838 <func_a+40>:      lw  ra,16(sp)

    -   0x8000083c <func_a+44>:      move   v0,zero

    /* v0 = 0 */

    -   0x80000840 <func_a+48>:      jr  ra

    JR Jump Register PC = Rs

        0x80000844 <func_a+52>:      addiu  sp,sp,24

        浏览下面代码发明一个问题:

        为什么ra = pc+8而不是pc+4呢?这样看来0x8000082c、0x80000834、0x80000844这3个地址对应的指令不会被执行到。

         

        下面这段来自《MIPS32 4K Processor Core Family SoftwareUser’s Manual》

        

    Jump and branch instructions change the control flow of a program. All jump and branch instructions occur with a delay of one instruction: that is, the instruction immediately following the jump or branch (this is known as the instruction in the delay slot) always executes while the target instruction is being fetched from storage.

        简略的说,因为MIPS的多级流水机制导致Jump和Branch指令前面的一个指令会被放在延时槽中,无条件执行。

         

         

        下面是一些网站上找到的描述:

        http://gcc.gnu.org/ml/gcc-help/2008-01/msg00059.html

        

    How to traceback call stack on MIPS arch?

     

    Gcc saves the frame pointer to fp(s8) register at the beginning of each function if compiling source with -O0. But  it won't do so if compiling source with -O2. Without frame pointers, can I trace back call stacks in current function context? Or is there any option which forces gcc to save frame pointers for MIPS arch?

     

    PRC

    2008/1/8

        这个问题是关于GCC优化的,看看下面这个表就清楚了。从实际测试情况看,fp(s8)也就是通用寄存器30可以用sp也就是通用寄存器29来代替,因为在函数领空(不包括子函数调用)的时候sp是保持固定值的,因为没有类似于x86的pop和push指令,该问题只着眼于当前函数上下文,没有考虑到向前追溯的问题。

        

    mips_fp_be-gcc -O0 -g test.c -o btO0

    mips_fp_be-objdump -S btO0 > asmO0.txt

    mips_fp_be-gcc -O2 -g test.c -o btO2

    mips_fp_be-objdump -S btO2 > asmO2.txt

    00400e0c <main>:

     

    int

    main (int argc, char **argv)

    {

      400e0c: 3c1c0fc0 lui  gp,0xfc0

      400e10: 279c79b4 addiu    gp,gp,31156

      400e14: 0399e021 addu gp,gp,t9

      400e18: 27bdffd8 addiu    sp,sp,-40

      400e1c: afbc0010 sw   gp,16(sp)

      400e20: afbf0020 sw   ra,32(sp)

      400e24: afbe001c sw   s8,28(sp)

      400e28: afbc0018 sw   gp,24(sp)

      400e2c: 03a0f021 move s8,sp

      400e30: afc40028 sw   a0,40(s8)

      400e34: afc5002c sw   a1,44(s8)

      print_backtrace (); 

      400e38: 8f9980a4 lw   t9,-32604(gp)

      400e3c: 00000000 nop

      400e40: 0320f809 jalr t9

      400e44: 00000000 nop

      400e48: 8fdc0010 lw   gp,16(s8)

      return 0;

      400e4c: 00001021 move v0,zero

    }

      400e50: 03c0e821 move sp,s8

      400e54: 8fbf0020 lw   ra,32(sp)

      400e58: 8fbe001c lw   s8,28(sp)

      400e5c: 03e00008 jr   ra

      400e60: 27bd0028 addiu    sp,sp,40

         ...

    00400de4 <main>:

     

    int

    main (int argc, char **argv)

    {

      400de4: 3c1c0fc0 lui  gp,0xfc0

      400de8: 279c79dc addiu    gp,gp,31196

      400dec: 0399e021 addu gp,gp,t9

      400df0: 27bdffe0 addiu    sp,sp,-32

      400df4: afbc0010 sw   gp,16(sp)

      400df8: afbf001c sw   ra,28(sp)

      400dfc: afbc0018 sw   gp,24(sp)

      print_backtrace (); 

      400e00: 8f9980a4 lw   t9,-32604(gp)

      400e04: 00000000 nop

      400e08: 0320f809 jalr t9

      400e0c: 00000000 nop

      400e10: 8fbc0010 lw   gp,16(sp)

      return 0;

    }

      400e14: 8fbf001c lw   ra,28(sp)

      400e18: 00001021 move v0,zero

      400e1c: 03e00008 jr   ra

      400e20: 27bd0020 addiu    sp,sp,32

         ...

         

         

        

    You need to use the unwinder.

     

    #include <unwind.h>

    #include <stdio.h>

     

    static _Unwind_Reason_Code

    backtrace_helper (struct _Unwind_Context *ctx, void *a)

    {

      void *ip = (void*)_Unwind_GetIP (ctx);

      fprintf (stdout, "   %p/n", ip);

      return _URC_NO_REASON;

     

    void

    print_backtrace (void)

    {

      _Unwind_Backtrace (backtrace_helper, NULL);

    }

     

    int

    main (int argc, char **argv)

    {

      print_backtrace (); 

      return 0;

    }

        该答复解释了下面的问题,提出用_Unwind_Backtrace函数来表现caller的地址,其实就MIPS而言对于单枝函数(没有子函数调用的函数)只要读ra寄存器的值就可以了,对于非单枝函数需要从堆栈里恢复出ra并表现。同样的问题这个函数也没有做向前的追溯。

         

        

    For that to work, you must compile all the code with -fexceptions.

     

    You could also try compiling all the code with -fno-omit-framepointer and writing your own unwinder.  I posted such an unwinder to java-patches@gcc.gnu.org several years ago.  Later versions of GCC are starting to do optimizations in the function prolog that make unwinding without the unwinder meta-data very difficult.

     

    David Daney

        该答复给出了2个GCC的参数,也是答复了下面的问题。

         

        根据MIPS寄存器定义和GCC生成的机器码可以得到网上描述的“MIPS不支持C函数的帧结构”。

        每日一道理
    “多难兴才”曾一度被人定为规律。请看:屈原被放逐而作《离骚》;司马迁受宫刑而作《史记》;欧阳修两岁丧父笃学而成才;曹雪芹举家食粥而写出了不朽的《红楼梦》;越王勾践卧薪尝胆而雪洗国耻;韩信遭胯下辱而统率百万雄兵……他们都是在与逆境搏斗中成为伟人的!

        我的理解是和x86的ESP和EBP寄存器比,MIPS的确是无法直观的从寄存器里找到当前情况下堆栈的底部,每个函数对应的栈的尺寸是由GCC计算出的,函数返回时的栈的恢复也是通过立即数的方式通过指令来实现,如(addiu    sp,sp,40),这样我们做BackTrace最重要的一个问题就是确定每级函数的堆栈尺寸。

         

        我觉得要确定每级函数的堆栈尺寸,只能通过解析机器码来实现。凑巧发明netbsd系统在内核代码中实现了对MIPS体系结构backtrace的支持,现在来分析下核心代码。

        代码在:

        http://cvsweb.netbsd.org/bsdweb.cgi/src/sys/arch/mips/mips/trap.c?rev=1.217.12.21&content-type=text/x-cvsweb-markup

        代码的全路径:

        /src/sys/arch/mips/mips/trap.c

         

        

    #define   MIPS_JR_RA          0x03e00008          /* instruction code for jr ra */

    #define   MIPS_JR_K0          0x03400008          /* instruction code for jr k0 */

    #define   MIPS_ERET 0x42000018          /* instruction code for eret */

     

    /*

     * Do a stack backtrace.

     * (*printfn)()  prints the output to either the system log,

     * the console, or both.

     */

    void

    stacktrace_subr(mips_reg_t a0, mips_reg_t a1, mips_reg_t a2, mips_reg_t a3,

        vaddr_t pc, vaddr_t sp, vaddr_t fp, vaddr_t ra,

        void (*printfn)(const char*, ...))

    {

              vaddr_t va, subr;

              unsigned instr, mask;

              InstFmt i;

              int more, stksize;

              unsigned int frames =  0;

              int foundframesize = 0;

    #ifdef DDB

              db_expr_t diff;

              db_sym_t sym;

    #endif

     

    /* Jump here when done with a frame, to start a new one */

    loop:

              stksize = 0;

              subr = 0;

              if (frames++ > 100) {

                        (*printfn)("/nstackframe count exceeded/n");

                        /* return breaks stackframe-size heuristics with gcc -O2 */

                        goto finish;        /*XXX*/

              }

     

              /* check for bad SP: could foul up next frame */

              if (sp & 3 || (intptr_t)sp >= 0) { /* 首先堆栈值应该4字节地址对齐,其次bit31应该为1表示内核空间*/

                        (*printfn)("SP 0x%x: not in kernel/n", sp);

                        ra = 0;

                        subr = 0;

                        goto done;

              }

     

              /* Check for bad PC */

              if (pc & 3 || (intptr_t)pc >= 0 || (intptr_t)pc >= (intptr_t)edata) {

                        (*printfn)("PC 0x%x: not in kernel space/n", pc);

                        ra = 0;

                        goto done;

              }

     

    #ifdef DDB

              /*

              * Check the kernel symbol table to see the beginning of

              * the current subroutine.

              */

              diff = 0;

              sym = db_search_symbol(pc, DB_STGY_ANY, &diff);

              if (sym != DB_SYM_NULL && diff == 0) {

                        /* check func(foo) __attribute__((__noreturn__)) case */

                        instr = kdbpeek(pc - 2 * sizeof(int));

                        i.word = instr;

                        if (i.JType.op == OP_JAL) {

                                   sym = db_search_symbol(pc - sizeof(int),

                                       DB_STGY_ANY, &diff);

                                   if (sym != DB_SYM_NULL && diff != 0)

                                             diff += sizeof(int);

                        }

              }

              if (sym == DB_SYM_NULL) {

                        ra = 0;

                        goto done;

              }

              va = pc - diff;

    #else /* 基本来到这里,我们没有这个存放符号信息的数据库 */

              /*

              * Find the beginning of the current subroutine by scanning backwards

              * from the current PC for the end of the previous subroutine.

              *

               * XXX This won't work well because nowadays gcc is so aggressive

              *     as to reorder instruction blocks for branch-predict.

              *     (i.e. 'jr ra' wouldn't indicate the end of subroutine)

              */

              /* 这里向前搜索OPCODE,直到找到0x03e00008或者产生地址越界(超出了代码段最小可能地址verylocore),这样做是不可靠的,因为是通过搜索上一个函数的特征机器码来确定当前函数的顶部,所以一个假设前提是前面还有函数*/

              va = pc;

              do {

                        va -= sizeof(int);

                        if (va <= (vaddr_t)verylocore)

                                   goto finish;

                        instr = kdbpeek(va);

                        if (instr == MIPS_ERET)

                                   goto mips3_eret;

              } while (instr != MIPS_JR_RA && instr != MIPS_JR_K0);

              /* skip back over branch & delay slot */

              va += sizeof(int); /*跳过延时槽*/

    mips3_eret:

              va += sizeof(int); /*跳过JR_RA指令 */

              /* skip over nulls which might separate .o files */

              while ((instr = kdbpeek(va)) == 0)

                        va += sizeof(int); /* 跳过无用的空指令 */

    #endif

              subr = va; /* 得到当前函数的首地址 */

     

              /* scan forwards to find stack size and any saved registers */

              stksize = 0;

              more = 3;

              mask = 0;

              foundframesize = 0;

              for (va = subr; more; va += sizeof(int),

                                         more = (more == 3) ? 3 : more - 1) {

                        /* stop if hit our current position */

                        if (va >= pc)

                                   break;

                        instr = kdbpeek(va);

                        i.word = instr;

                        switch (i.JType.op) {

                        case OP_SPECIAL:

                                   switch (i.RType.func) {

                                   case OP_JR:

                                   case OP_JALR:

                                             more = 2; /* stop after next instruction */

                                             break;

     

                                   case OP_SYSCALL:

                                   case OP_BREAK:

                                             more = 1; /* stop now */

                                   };

                                   break;

     

                        case OP_BCOND:

                        case OP_J:

                        case OP_JAL:

                        case OP_BEQ:

                        case OP_BNE:

                        case OP_BLEZ:

                        case OP_BGTZ:

                                   more = 2; /* stop after next instruction */

                                   break;

     

                        case OP_COP0:

                        case OP_COP1:

                        case OP_COP2:

                        case OP_COP3:

                                   switch (i.RType.rs) {

                                   case OP_BCx:

                                   case OP_BCy:

                                             more = 2; /* stop after next instruction */

                                   };

                                   break;

     

                        case OP_SW:/* 解析存放在堆栈上的有用数据:包括4个传递参数的寄存器、帧指针、函数返回地址*/

    #if !defined(__mips_o32)

                        case OP_SD:

    #endif

                        {

                                   size_t size = (i.JType.op == OP_SW) ? 4 : 8;

     

                                   /* look for saved registers on the stack */

                                   if (i.IType.rs != 29)

                                             break;

                                   /* only restore the first one */

                                   if (mask & (1 << i.IType.rt))

                                             break;

                                   mask |= (1 << i.IType.rt);

                                   switch (i.IType.rt) {

                                   case 4: /* a0 */

                                             a0 = kdbrpeek(sp + (short)i.IType.imm, size);

                                             break;

     

                                   case 5: /* a1 */

                                             a1 = kdbrpeek(sp + (short)i.IType.imm, size);

                                             break;

     

                                   case 6: /* a2 */

                                             a2 = kdbrpeek(sp + (short)i.IType.imm, size);

                                             break;

     

                                   case 7: /* a3 */

                                             a3 = kdbrpeek(sp + (short)i.IType.imm, size);

                                             break;

     

                                   case 30: /* fp */

                                             fp = kdbrpeek(sp + (short)i.IType.imm, size);

                                             break;

     

                                   case 31: /* ra */

                                             ra = kdbrpeek(sp + (short)i.IType.imm, size);

                                   }

                                   break;

                        }

     

                        case OP_ADDI:

                        case OP_ADDIU: /* 这里来分析堆栈的尺寸,是一个类似于addiu    sp,sp,-24的指令,我们要将立即数取出,并负负得正 */

    #if !defined(__mips_o32)

                        case OP_DADDI:

                        case OP_DADDIU:

    #endif

                                   /* look for stack pointer adjustment */

                                   if (i.IType.rs != 29 || i.IType.rt != 29)

                                             break;

                                   /* don't count pops for mcount */

                                   if (!foundframesize) {

                                             stksize = - ((short)i.IType.imm);

                                             foundframesize = 1;

                                   }

                        }

              }

    done:

              (*printfn)("%s+%"PRIxVADDR" (%"PRIxREGISTER",%"PRIxREGISTER",%"PRIxREGISTER",%"PRIxREGISTER") ra %"PRIxVADDR" sz %d/n",

                        fn_name(subr), pc - subr, a0, a1, a2, a3, ra, stksize);

     

              if (ra) {

                        if (pc == ra && stksize == 0)/* 出现堆栈长度为零并且当前程序指针为返回地址,则出现循环调用,为异常情况,应直接返回 */

                                   (*printfn)("stacktrace: loop!/n");

                        else {

                                   pc = ra;

                                   sp += stksize;

                                   ra = 0;

                                   goto loop;

                        }

              } else {/* 返回地址为零表示已经追溯到最顶层 */

    finish:

                        if (curlwp)

                                   (*printfn)("User-level: pid %d.%d/n",

                                       curlwp->l_proc->p_pid, curlwp->l_lid);

                        else

                                   (*printfn)("User-level: curlwp NULL/n");

              }

    }

     

         

        下面演示将下面的函数移植到Linux下,应用程序出现异常时的BackTrace表现

         

        应用程序代码:

        

    #include <stdio.h>

    #include <stdlib.h>

    #include <signal.h>

     

    extern int sig_set(int signo);

    int func_a(unsigned char* a, unsigned char * b, int c, int d);

    int func_b(unsigned char* a);

    int func_c(unsigned char* a);

     

    int func_a(unsigned char* a, unsigned char * b, int c, int d)

    {

        func_b(a);

        return 0;

    }

     

    int func_b(unsigned char* a)

    {

        func_c(a);

        return 0;

    }

     

    int func_c(unsigned char* a)

    {

        *a = "Hello";

        return 0;

    }

     

    int main(int argc, char* argv[])

    {

        unsigned char* a;

        unsigned char buffer[128];

        a = NULL;

            if( sig_set(SIGSEGV) != 0)

                    printf("cannot catch SIGSEGV/n");

            if( sig_set(SIGILL) != 0)

                    printf("cannot catch SIGILL/n");

     

            printf("trying to catch SIGFPE/n");

            if( sig_set(SIGFPE) != 0)

                    printf("cannot catch SIGFPE/n");

     

            printf("trying to catch SIGBUS/n");

            if( sig_set(SIGBUS) != 0)

                    printf("cannot catch SIGBUS/n");

     

        func_a(a, buffer, 2, 3);

        return 0;

    }

        先注册了4个信号SIGSEGV、SIGILL、SIGFPE、SIGBUS用自己的处置函数来处置。

        在函数func_c调用的时候会出现异常,因为a的地址为NULL,这时将整个函数调用的过程表现出来,输入如下:

        

    fSegvHandler default sigNo [11]

    pc=00400a84 cause 00000003 badaddr 00000000

     

    d00:00000000 d01:7fff7bd0 d02:00401ee0 d03:00000000

    d04:00000000 d05:7fff7d08 d06:00000002 d07:00000003

    d08:0000d500 d09:0000000a d10:00000000 d11:00000000

    d12:00001000 d13:00000000 d14:0000000a d15:15010000

    d16:00401d40 d17:7fff7df4 d18:00401ca0 d19:00000001

    d20:00400a9c d21:10012608 d22:ffffffff d23:00000000

    d24:00000000 d25:00400a4c d26:00000010 d27:00000000

    d28:10008040 d29:7fff7c88 d30:7fff7c88 d31:00400a30

    k0(d26):00000010

    k1(d27):00000000 gp(d28):10008040

    sp(d29):7fff7c88

    fp(d30):7fff7c88 ra(d31):00400a30

    hi:00000000     lo:00000000

     

    Calling backtrace:

    Func [400a4c] PC [400a84] Arg0~3 (0,7fff7d08,2,3) RetAddr [400a30] stackSize [16]

    Func [4009f4] PC [400a30] Arg0~3 (0,7fff7d08,2,3) RetAddr [4009d8] stackSize [40]

    Func [400990] PC [4009d8] Arg0~3 (0,7fff7d08,2,3) RetAddr [400c38] stackSize [40]

    Func [400a9c] PC [400c38] Arg0~3 (0,7fff7d08,2,3) RetAddr [2ab18b50] stackSize [176]

    Func [2ab189b0] PC [2ab18b50] Arg0~3 (0,7fff7d08,2,3) RetAddr [400790] stackSize [32]

    Func [400790] PC [400790] Arg0~3 (0,7fff7d08,2,3) RetAddr [0] stackSize [0]

    finished

    Segmentation fault

        下面输出的结果可以和实际情况对应起来:

        这里是函数调用栈上各个函数的基地址

        

    -bash-3.00$ mips_fp_be-objdump -t test1 | grep 400a4c

    00400a4c g     F .text  00000000              func_c

    -bash-3.00$ mips_fp_be-objdump -t test1 | grep 4009f4

    004009f4 g     F .text  00000000              func_b

    -bash-3.00$ mips_fp_be-objdump -t test1 | grep 400990

    00400990 g     F .text  00000000              func_a

    -bash-3.00$ mips_fp_be-objdump -t test1 | grep 400a9c

    00400a9c g     F .text  00000000              main

    -bash-3.00$ mips_fp_be-objdump -t test1 | grep 400790

    00400790 g     F .text  00000000              __start

        PC表示当前函数执行的地址,RetAddr为函数返回地址(和上级函数的PC对应)

        例如第一行的输出:

        Func [400a4c] PC[400a84] Arg0~3 (0,7fff7d08,2,3) RetAddr [400a30] stackSize [16]

        对应于下面反汇编出来的代码,看的更清楚。

        

    int func_c(unsigned char* a)

    {

      400a4c:   3c1c0fc0    lui gp,0xfc0

      400a50:   279c75f4    addiu   gp,gp,30196

      400a54:   0399e021    addu    gp,gp,t9

      400a58:   27bdfff0    addiu   sp,sp,-16

      400a5c:   afbc0000    sw  gp,0(sp)

      400a60:   afbe000c    sw  s8,12(sp)

      400a64:   afbc0008    sw  gp,8(sp)

      400a68:   03a0f021    move    s8,sp

      400a6c:   afc40010    sw  a0,16(s8)

        *a = "Hello";

      400a70:   8fc30010    lw  v1,16(s8)

      400a74:   8f828018    lw  v0,-32744(gp)

      400a78:   00000000    nop

      400a7c:   24421ee0    addiu   v0,v0,7904

      400a80:   00000000    nop

      400a84:   a0620000    sb  v0,0(v1)

        return 0;

      400a88:   00001021    move    v0,zero

    }

      400a8c:   03c0e821    move    sp,s8

      400a90:   8fbe000c    lw  s8,12(sp)

      400a94:   03e00008    jr  ra

      400a98:   27bd0010    addiu   sp,sp,16

         

         

        

    int func_b(unsigned char* a)

    {

      4009f4:   3c1c0fc0    lui gp,0xfc0

      4009f8:   279c764c    addiu   gp,gp,30284

      4009fc:   0399e021    addu    gp,gp,t9

      400a00:   27bdffd8    addiu   sp,sp,-40

      400a04:   afbc0010    sw  gp,16(sp)

      400a08:   afbf0020    sw  ra,32(sp)

      400a0c:   afbe001c    sw  s8,28(sp)

      400a10:   afbc0018    sw  gp,24(sp)

      400a14:   03a0f021    move    s8,sp

      400a18:   afc40028    sw  a0,40(s8)

        func_c(a);

      400a1c:   8fc40028    lw  a0,40(s8)

      400a20:   8f998030    lw  t9,-32720(gp)

      400a24:   00000000    nop

      400a28:   0320f809    jalr    t9

      400a2c:   00000000    nop

      400a30:   8fdc0010    lw  gp,16(s8)

        return 0;

      400a34:   00001021    move    v0,zero

    }

      400a38:   03c0e821    move    sp,s8

      400a3c:   8fbf0020    lw  ra,32(sp)

      400a40:   8fbe001c    lw  s8,28(sp)

      400a44:   03e00008    jr  ra

      400a48:   27bd0028    addiu   sp,sp,40

         

    文章结束给大家分享下程序员的一些笑话语录: AdobeFlash拖垮Windows拖垮IE!又拖垮Linux拖垮Ubuntu拖垮FirxEox!还拖垮BSD拖垮MacOS拖垮Safri!简直无所不拖!AdobeFlash滚出网路世界!不要以为市占有率高就可以持续出烂货产品!以后替代品多得是!

  • 相关阅读:
    day08
    day07
    day06
    day06
    day05
    第三次作业
    第三次作业
    第二次作业
    java 数字和日期处理
    jsp文件导包
  • 原文地址:https://www.cnblogs.com/jiangu66/p/3074202.html
Copyright © 2020-2023  润新知