基礎知識介紹:
- 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; and shadow 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-related exception
cause (CP0 Reg13) Cause of last exception
pc 很明顯這個是程序計數寄存器,奇怪的是在32個通用寄存器以及CP0的32個寄存器中都沒有找到他,最接近的一個是CP0Reg14 EPC (Program counter at last exception.)
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 registers and 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 */ }; |
- 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 Software User’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的支持,現在來分析下核心代碼。
代碼在:
代碼的全路徑:
/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