arm平臺的調用棧回溯(backtrace)

介紹

arm平臺的調用棧與x86平臺的調用棧大體相同,稍微有些區別,主要在於棧幀的壓棧內容和傳參方式不一樣。在arm平臺的不一樣程序,採用的編譯選項不一樣,程序運行期間的棧幀也會不一樣。有些工具在對arm的調用棧回溯時,可能會遇到沒法回溯的狀況。例如gdb在使用bt查看core dump文件調用棧時,有時會出現Backtrace stoped的狀況,有可能就是棧空間的壓棧順序致使的。當工具沒法回溯時,就須要人工結合彙編代碼對棧進行回溯,或者使用unwind進行回溯。html

arm棧幀結構

arm的一種棧結構

一般狀況下,arm的調用棧大體結構與x86相同,都是從高地址向低地址擴張。上圖是其中一種內存分佈。架構

pc, lr, sp, fp是處理器的寄存器,其含義以下:app

  • pc, program counter,程序計數器。程序當前運行的指令會放入到pc寄存器中
  • fp, 即frame pointer,幀指針。一般指向一個函數的棧幀底部,表示一個函數棧的開始位置。
  • sp, stack pointer,棧頂指針。指向當前棧空間的頂部位置,當進行push和pop時會一塊兒移動。
  • lr, link register。在進行函數調用時,會將函數返回後要執行的下一條指令放入lr中,對應x86架構下的返回地址。

調用棧從高地址向低地址增加,當函數調用時,分別將分別將pc, lr, ip和 fp寄存器壓入棧中,而後移動sp指針,爲當前程序開闢棧空間。jsp

arm官方手冊描述以下:函數

一個arm程序,在任一時刻都存在十五個通用寄存器,這取決於當前的處理器模式。 它們分別是 r0-r十二、sp、lr。 sp(或 r13)是堆棧指針。 C 和 C++ 編譯器始終將 sp 用做堆棧指針。 在 Thumb-2 中,sp 被嚴格定義爲堆棧指針,所以許多對堆棧操做無用而又使用了 sp 的指令會產生不可預測的結果。 建議您不要將 sp 用做通用寄存器。 在用戶模式下,lr(或 r14)用做連接寄存器 (lr),用於存儲調用子例程時的返回地址。 若是返回地址存儲在堆棧上,則也可將 r14 用做通用寄存器。 在異常處理模式下,lr 存放異常的返回地址;若是在一個異常內執行了子例程調用,則 lr 存放子例程的返回地址。若是返回地址存儲在堆棧上,則可將 lr 用做通用寄存器。工具

除了官方手冊中描述的sp,lr寄存器,一般r12還會做爲fp寄存器。fp寄存器對於程序的運行沒有幫助,主要用於對棧幀的回溯。由於sp時刻指向的棧頂,經過fp得知上一個棧幀的起始位置。ui

上圖的調用棧對應的彙編代碼以下。this

  1. 8514行將當前的sp保存在ip中(ip只是個通用寄存器,用來在函數間分析和調用時暫存數據,一般爲r12);
  2. 8518行將4個寄存器從右向左依次壓棧。
  3. 851c行將保存的ip減4,獲得當前被調用函數的fp地址,即指向棧裏的pc位置。
  4. 8520行將sp減8,爲棧空間開闢出8個字節的大小,用於存放局部便令。
00008514 <func1>:
     8514:   e1a0c00d    mov ip, sp
     8518:   e92dd800    push    {fp, ip, lr, pc}
     851c:   e24cb004    sub fp, ip, #4
     8520:   e24dd008    sub sp, sp, #8
     8524:   e3a03000    mov r3, #0
     8528:   e50b3010    str r3, [fp, #-16]
     852c:   e30805dc    movw    r0, #34268  ; 0x85dc
     8530:   e3400000    movt    r0, #0
     8534:   ebffff9d    bl  83b0 <puts@plt>
     8538:   e51b3010    ldr r3, [fp, #-16]
     853c:   e12fff33    blx r3
     8540:   e3a03000    mov r3, #0
     8544:   e1a00003    mov r0, r3
     8548:   e24bd00c    sub sp, fp, #12
     854c:   e89da800    ldm sp, {fp, sp, pc}

-mapcs-frame編譯選項

在第一節中,程序壓棧的寄存器有{fp, ip, lr, pc} 4個,這是在gcc帶有-mapcs-frame的編譯選項下編譯出來的。而gcc默認狀況下的參數爲mno-apcs-frame。關於該選項,gcc的手冊描述爲,url

Generate a stack frame that is compliant with the ARM Procedure Call Standard for all functions, even if this is not strictly necessary for correct execution of the code. Specifying -fomit-frame-pointer with this option causes the stack frames not to be generated for leaf functions. The default is -mno-apcs-frame. This option is deprecated.spa

也就是說,該編譯選項會產生(push {fp, ip, lr, pc}),保證棧幀的格式。若是沒有-mapcs-frame,則不保證幀格式和當前幀格式,GCC生成的指令可能會發生各類變化。在AAPCS發佈以後[附錄1],1993年的APCS就已經太舊了,因此 在gcc5.0以後,該選項已經被廢棄。gcc5.0的更新記錄寫到:

The options -mapcs, -mapcs-frame, -mtpcs-frame and -mtpcs-leaf-frame which are only applicable to the old ABI have been deprecated. 至於該參數在未來是否會被gcc移除,那就不知道了。

將第一節中的程序從新使用默認編譯選項,用4.7版本的gcc編譯,結果以下。這時,fp還在,調用棧push了fp和lr到棧空間,新的fp指向了lr在棧中的位置。

00008514 <func1>:
     8514:   e92d4800    push    {fp, lr}
     8518:   e28db004    add fp, sp, #4
     851c:   e24dd008    sub sp, sp, #8
     8520:   e3a03000    mov r3, #0
     8524:   e50b3008    str r3, [fp, #-8]
     8528:   e30805d4    movw    r0, #34260  ; 0x85d4
     852c:   e3400000    movt    r0, #0
     8530:   ebffff9e    bl  83b0 <puts@plt>
     8534:   e51b3008    ldr r3, [fp, #-8]
     8538:   e12fff33    blx r3
     853c:   e3a03000    mov r3, #0
     8540:   e1a00003    mov r0, r3
     8544:   e24bd004    sub sp, fp, #4
     8548:   e8bd8800    pop {fp, pc}

 0000854c <main>:
     854c:   e92d4800    push    {fp, lr}
     8550:   e28db004    add fp, sp, #4
     8554:   ebffffee    bl  8514 <func1>
     8558:   e1a00003    mov r0, r3
     855c:   e8bd8800    pop {fp, pc}

使用gcc-7.3默認選項編譯結果以下,fp已經不在了,雖然這裏仍然可能經過r7得知上個棧幀的位置,可是已經無法使用fp獲取棧幀了。此時是不保證棧幀保存在棧中的。因此依賴棧幀內容進行恢復已經很是不可靠。那麼既然沒法依賴fp,那該怎麼進行棧幀回溯呢,gnu說使用unwind方法回溯,這節暫時不會介紹unwind方法。

000103c8 <func1>:
    103c8:   b580        push    {r7, lr}
    103ca:   b082        sub sp, #8
    103cc:   af00        add r7, sp, #0
    103ce:   2300        movs    r3, #0
    103d0:   607b        str r3, [r7, #4]
    103d2:   f240 4048   movw    r0, #1096   ; 0x448
    103d6:   f2c0 0001   movt    r0, #1
    103da:   f7ff ef7e   blx 102d8 <puts@plt>
    103de:   687b        ldr r3, [r7, #4]
    103e0:   4798        blx r3
    103e2:   2300        movs    r3, #0
    103e4:   4618        mov r0, r3
    103e6:   3708        adds    r7, #8
    103e8:   46bd        mov sp, r7
    103ea:   bd80        pop {r7, pc}

 000103ec <main>:
    103ec:   b580        push    {r7, lr}
    103ee:   af00        add r7, sp, #0
    103f0:   f7ff ffea   bl  103c8 <func1>
    103f4:   2300        movs    r3, #0
    103f6:   4618        mov r0, r3
    103f8:   bd80        pop {r7, pc}

使用棧幀進行回溯

這一節使用gcc4.7版本,默認編譯選項編譯出來的程序,演示調用棧回溯。該編譯選項下,壓棧的寄存器爲{fp, lr}。

下邊的內容是一段core dump中的寄存器和調用棧,本節將對這段內容進行回溯。

Reg:         r9, Val = 0xf7578000;  Reg:        r10, Val = 0x00000001;  
Reg:         fp, Val = 0x827d3104;  Reg:         ip, Val = 0xf7578ae0;  
Reg:         sp, Val = 0x827d30e0;  Reg:         lr, Val = 0xf7549990;  
Reg:         pc, Val = 0xf7548c20;  Reg:       cpsr, Val = 0x60000210;

0x827d30e0:	0x00000031	0x827d31a0	0x00000001	0xd5dff060	
0x827d30f0:	0xd5e0e6b1	0xd5dec134	0xf7578000	0xf7577c40	
0x827d3100:	0x827d313c	0xf7549990		
0x827d3140:	0x00000000	0xd5dec104	0xf7568514	0x00000002	
0x827d3150:	0xd5dec104	0xf7577c40	0xf7577c38	0xd5de9224	
0x827d3160:	0x827d31a0	0xf757a084	0xf7577c40	0xd5df6dd4	
0x827d3170:	0x827d3194	0x00000001	0xd5e0e678	0xd5dec104	
0x827d3180:	0xd5de9224	0xf7568548	0x00000000	0xf7568550
  1. 當前sp地址爲0x827d30e0,fp地址爲0x827d3104,從而得知當前函數frame0的棧幀。fp指向的地址0x827d3104爲frame1的lr,0x827d3100爲上一個棧幀的fp。
0x827d30e0:	0x00000031	0x827d31a0	0x00000001	0xd5dff060	
0x827d30f0:	0xd5e0e6b1	0xd5dec134	0xf7578000	0xf7577c40	
0x827d3100:	0x827d313c(fp)	0xf7549990(lr)
  1. 從frame0的fp地址0x827d313c可知,frame1的調用棧起始地址,去掉frame0的內容,獲得frame1的棧幀。
0x827d312c	0xf7530c14	
0x827d3110:	0xd5dff060	0x0000002c	0xd5e0e6b1	0xd5e0e6b1	
0x827d3120:	0x00000001	0xd5e0e6b1	0xd5dff060	0xd5dec134	
0x827d3130:	0xf7578000	0xf7577c40	0x827d3194(fp)	0xf754ad0c(lr)
  1. 依次類推,依次獲得frame二、frame3...的棧幀。

當彙編代碼的函數調用使用push {fp, ip, lr, pc}時,則上一個棧幀的fp2在當前棧幀的(fp - #4)位置。棧幀的回溯要結合程序的彙編代碼具體分析,有可能程序並不使用fp指針,也有可能棧中根本沒有保存fp。

unwind方法回溯

TODO

附錄1-函數調用標準縮略語

  • PCS Procedure Call Standard.
  • AAPCS Procedure Call Standard for the ARM Architecture (this standard).
  • APCS ARM Procedure Call Standard (obsolete).
  • TPCS Thumb Procedure Call Standard (obsolete).
  • ATPCS ARM-Thumb Procedure Call Standard (precursor to this standar

參考資料

  1. ARM 體系結構概述
  2. Procedure Call Standard for the ARM® Architecture
  3. GCC 5 Release Series
相關文章
相關標籤/搜索