mips體系堆棧回溯分析與實現


轉載:http://www.sohu.com/a/118385096_468740

mips棧幀原理

Call stack 是指存放某個程序的正在運行的函數的信息的棧。Call stack 由 stack frames 組成,每個 stack frame 對應於一個未完成運行的函數。

在當今流行的計算機體系架構中,大部分計算機的參數傳遞,局部變量的分配和釋放都是通過操縱程序棧來實現的。棧用來傳遞函數參數,存儲返回值信息,保存寄存器以供恢復調用前處理機狀態。 關於棧可見以前的文章: cdecl、stdcall、fastcall函數調用約定區別

每次調用一個函數,都要為該次調用的函數實例分配棧空間。為單個函數分配的那部分棧空間就叫做 stack frame,也就是說,stack frame 這個說法主要是為了描述函數調用關系的。

Stack frame 具體從2點來闡述

第一,它使調用者和被調用者達成某種約定。這個約定定義了函數調用時函數參數的傳遞方式,函數返回值的返回方式,寄存器如何在調用者和被調用者之間進行共享;

第二,它定義了被調用者如何使用它自己的 stack frame 來完成局部變量的存儲和使用。

上圖描述的是一種典型的(MIPS O32)嵌入式芯片的 stack frame 組織方式。在這張圖中,計算機的棧空間采用的是向下增長的方式(intel 是確定向下增長的,arm可以配置棧增長方向,mips 是可配置還是只能向下增長?),SP(stack pointer) 就是當前函數的棧指針,它指向的是棧底的位置。Current Frame 所示即為當前函數(被調用者)的 frame ,Caller’s Frame 是當前函數的調用者的 frame 。

在沒有 BP(base pointer) 寄存器的目標架構中,進入一個函數時需要將當前棧指針向下移動 n 字節,這個大小為n字節的存儲空間就是此函數的 stack frame 的存儲區域。此后棧指針便不再移動(在linux 內核代碼 TODO 里面寫着要加上在函數內部調整棧的考慮 – 雖然這通常不會發生),只能在函數返回時再將棧指針加上這個偏移量恢復棧現場。由於不能隨便移動棧指針,所以寄存器壓棧和出棧都必須指定偏移量,這與 x86 架構的計算機對棧的使用方式有着明顯的不同。

RISC計算機一般借助於一個返回地址寄存器 RA(return address) 來實現函數的返回。幾乎在每個函數調用中都會使用到這個寄存器,所以在很多情況下 RA 寄存器會被保存在堆棧上以避免被后面的函數調用修改,當函數需要返回時,從堆棧上取回 RA 然后跳轉。移動 SP 和保存寄存器的動作一般處在函數的開頭,叫做 function prologue;

注意如果當前函數是葉子函數(不存在對其它函數的調用,就不保存ra寄存器,反之就保存)。恢復這些寄存器狀態的動作一般放在函數的最后,叫做 function epilogue。關於這些動作可以從IDA 反匯編的結果看出來:

通過上面分析就有思路了:

首先獲取當前的棧指針 sp,和指令指針 pc (也叫做 IP)

在mips下sp容易獲取 已經約定$29 寄存器作為棧指針,所以可用如下內嵌匯編獲取sp:

__asm__ volatile ("move %0, $29" : "=r"(reg));

MIPS沒有記錄當前PC地址的寄存器,就是說不能像ARM那樣讀PC寄存器。MIPS使用ra保存函數返回地址,利用這個特性可以獲取到當前的PC。 比如:

#pragma GCC push_options

#pragma GCC optimize ("O0")

static unsigned int /*__attribute__((optimize("O0")))*/ * __getpc(void)

{

unsigned int *rtaddr;

__asm__ volatile ("move %0, $31" : "=r"(rtaddr));

return rtaddr;

}

#pragma GCC pop_options

對應的匯編代碼是:

注意到上面代碼中有 push_options 這是為了防止編譯器偷偷把我們的代碼給優化成 inline 了,那樣就無法獲取pc了。在要獲取pc的地方調用就可以了:

pc = __getpc();

得到sp 和pc之后剩下的就開始回溯了,具體參考下面函數實現(linux 內核未修改代碼,只加入注釋):

/*

* TODO for userspace stack unwinding:

* - handle cases where the stack is adjusted inside a function

* (generally doesn't happen)

* - find optimal value for max_instr_check

* - try to find a way to handle leaf functions

*/

static inline int unwind_user_frame(struct stackframe *old_frame,

const unsigned int max_instr_check) //// max_instr_check 函數最大可能的代碼長度

{

struct stackframe new_frame = *old_frame;

off_t ra_offset = 0;

size_t stack_size = 0;

unsigned long addr;

if (old_frame->pc == 0 || old_frame->sp == 0 || old_frame->ra == 0)

return -9;

for (addr = new_frame.pc; (addr + max_instr_check > new_frame.pc) /// 上面通過 __getpc 獲取的pc指針

&& (!ra_offset || !stack_size); --addr) {

union mips_instruction ip;

if (get_mem(addr, (unsigned long *) &ip)) /// 取出一條指令 ip

return -11;

if (is_sp_move_ins(&ip)) { /// 這條指令是不是 addiu $sp,imme 形式的

int stack_adjustment = ip.i_format.simmediate; /// 如果是求出 imme 立即數,那么本函數棧大小也就知道了

if (stack_adjustment > 0)

/* This marks the end of the previous function,

which means we overran. */

break;

stack_size = (unsigned long) stack_adjustment;

} else if (is_ra_save_ins(&ip)) { /// 是不是 sw / sd $ra, offset($sp) 類似的指令

int ra_slot = ip.i_format.simmediate; /// 如果是獲取 offset

if (ra_slot < 0)

/* This shouldn't happen. */

break;

ra_offset = ra_slot;

} else if (is_end_of_function_marker(&ip))

break;

}

if (!ra_offset || !stack_size)

return -1;

if (ra_offset) {

new_frame.ra = old_frame->sp + ra_offset; /// 根據上面的 offset 和當前函數的sp指針得到存放 ra 數值的地址

if (get_mem(new_frame.ra, &(new_frame.ra))) /// 獲取ra 的數值,也就是 jal func 這條指令所在的地址

return -13;

}

if (stack_size) {

new_frame.sp = old_frame->sp + stack_size; /// 上個函數的棧指針 貌似這里這么代碼是錯誤的,反正我按我的方式改了能正確運行,

if (get_mem(new_frame.sp, &(new_frame.sp))) /// 這里這么寫我也不懂。

return -14;

}

if (new_frame.sp > old_frame->sp)

return -2;

new_frame.pc = old_frame->ra;

*old_frame = new_frame;

return 0;

}

上面代碼修改之后可以獲取 上一層函數的 pc (jal func :調用本函數的指令不就是上層函數的某一條指令? ra 的值並不是jal func 的地址,而是jal func的地址 +8 ,傳說中的 跳轉延時槽)指針和sp 。

依此循環調用上面的函數一層層向上回溯,即可。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM