今天和大家一起看下面對 crash 日志的時候,如何利用 stack 來分析其變化的來龍去脈。
Arm指令集介紹崇尚簡單粗暴的介紹方式,我們直接來看各個寄存器的大體用法,詳細用法可百度,不,谷歌。
1. ?? r0-r3 用作傳入函數(shù)參數(shù),傳出函數(shù)返回值。在子程序調用之間,可以將 r0-r3 用于任何用途。被調用函數(shù)在返回之前不必恢復 r0-r3。---如果調用函數(shù)需要再次使用 r0-r3 的內容,則它必須保留這些內容。2. ?? r4-r11 被用來存放函數(shù)的局部變量。如果被調用函數(shù)使用了這些寄存器,它在返回之前必須恢復這些寄存器的值。r11 是棧幀指針?fp。3.????r12 是內部調用暫時寄存器?ip。它在過程鏈接膠合代碼(例如,交互操作膠合代碼)中用于此角色。在過程調用之間,可以將它用于任何用途。被調用函數(shù)在返回之前不必恢復 r12。4.????寄存器 r13 是棧指針?sp。它不能用于任何其它用途。sp 中存放的值在退出被調用函數(shù)時必須與進入時的值相同。5.????寄存器 r14 是鏈接寄存器?lr。如果您保存了返回地址,則可以在調用之間將 r14 用于其它用途,程序返回時要恢復6.????寄存器 r15 是程序計數(shù)器?pc。它不能用于任何其它用途。
演示代碼假如現(xiàn)在你已經掌握了 arm 指令的用法,即便沒有掌握也沒關系,“書到用時回頭翻”。這里以一段簡單的 c 語言為例:
#include
int m = 8;int fun(int a,int b){ int c = 0; c = a b; return c;}int main(){ int i = 4; int j = 5; m = fun(i, j); return 0;}編譯一下,然后反匯編:$ arm-linux-gnueabi-gcc main.c -o main?$ arm-linux-gnueabi-objdump -D -D main
00010400 : 10400: e52db004 push {fp} ; (str fp, [sp, #-4]!) 10404: e28db000 add fp, sp, #0 10408: e24dd014 sub sp, sp, #20 1040c: e50b0010 str r0, [fp, #-16] 10410: e50b1014 str r1, [fp, #-20] ; 0xffffffec 10414: e3a03000 mov r3, #0 10418: e50b3008 str r3, [fp, #-8] 1041c: e51b2010 ldr r2, [fp, #-16] 10420: e51b3014 ldr r3, [fp, #-20] ; 0xffffffec 10424: e0823003 add r3, r2, r3 10428: e50b3008 str r3, [fp, #-8] 1042c: e51b3008 ldr r3, [fp, #-8] 10430: e1a00003 mov r0, r3 10434: e24bd000 sub sp, fp, #0 10438: e49db004 pop {fp} ; (ldr fp, [sp], #4) 1043c: e12fff1e bx lr
00010440 : 10440: e92d4800 push {fp, lr} 10444: e28db004 add fp, sp, #4 10448: e24dd008 sub sp, sp, #8 1044c: e3a03004 mov r3, #4 10450: e50b300c str r3, [fp, #-12] 10454: e3a03005 mov r3, #5 10458: e50b3008 str r3, [fp, #-8] 1045c: e51b1008 ldr r1, [fp, #-8] 10460: e51b000c ldr r0, [fp, #-12] 10464: ebffffe5 bl 10400 10468: e1a02000 mov r2, r0 1046c: e59f3010 ldr r3, [pc, #16] ; 10484 10470: e5832000 str r2, [r3] 10474: e3a03000 mov r3, #0 10478: e1a00003 mov r0, r3 1047c: e24bd004 sub sp, fp, #4 10480: e8bd8800 pop {fp, pc} 10484: 00021024 andeq r1, r2, r4, lsr #32圖解棧的變化過程如何能讓讀者接受吸收的更快,我一直覺得按照學習效率來講的話順序應該是視頻,圖文,文字。反正我是比較喜歡視頻類的教學。這里給大家畫下棧變化的過程是什么樣子的。這里的圖是結合上面的代碼來畫的,希望有助于讀者的理解。
1.程序在內存分布區(qū)域
2.全局變量m賦值
3.保存進入main之前的棧底, fp-sp之間是當前函數(shù)棧
4.函數(shù)main的棧已經準備好了
5.i入棧
6.j入棧
7.準備函數(shù)fun的調用, 形參反向入棧 先形參b入棧
8.形參a入棧
9.留空一個地址作為fun返回值, 待后面返回時填入
10.fun返回地址入棧, 通常是main函數(shù)當前pc指針的下一個
11.main函數(shù)的棧底地址入棧
12.pc指針跳轉fun代碼
13.c入棧
14.可以看到函數(shù)fun的數(shù)據(jù) 形參a,b 在上一層函數(shù)的棧中. 一部分在自己的棧上. 此步取值到加法器中進行加法運算,再賦值給c
15.c賦給返回值,填入上面的留空位置
16.棧底恢復上一層
17.lr賦值給pc, 實現(xiàn)了跳轉
18.返回值賦值給全局變量m
19.前面函數(shù)調用的形參已經無用,回滾sp
20.函數(shù)返回,清理main的棧空間
總結這么多圖有沒有看花?相信到這里你已經了解了棧背后的來龍去脈,下一篇我們一起根據(jù)實際的 stack 錯誤案例剖析錯誤的可能性。
往期推薦:物聯(lián)網(wǎng)通信協(xié)議大匯總!
極客感十足的電子胸牌 ART-Badge V2.0
點擊閱讀原文,查看更多分享





