arm堆棧操作 arm堆棧的組織結構是 滿棧降 的形式,滿棧即sp是要停留在最后一個進棧元素,降:就是堆棧的增長方向是從高地址向低地址發展。 arm對于堆棧的操作一般采用 LDMFD(pop)和STMFD (push) 兩個命令。 以前困惑的就是STMFD 命令 對于操作數 是按照什么順序壓棧的 比如:STMFD sp!{R0-R5,LR} 進棧順序是: 高地址(1方式) LR R5 R4 ``````` R0 <-sp 低地址 還是: 高地址(2方式) R0 R1 ``` R5 LR <-sp 低地址 現在通過下表,可以輕松的解決這個問題:
按照圖表,可知 STMFD對應的是STMDB,根據arm指令手冊,可知STMDB入棧順序是(1方式) 而LDMFD對應的是LDMIA,這樣這兩個操作就可以成功配對: 以下是我在學習ARM指令中記錄的關于堆棧方面的知識1、寄存器 R13 在 ARM 指令中常用作堆棧指針 2、對于 R13 寄存器來說,它對應6個不同的物理寄存器,其中的一個是用戶模式與系統模式共用,另外5個物理寄存器對應于其他5種不同的運行模式。采用以下的記號來區分不同的物理寄存器: R13_ 3、寄存器R13在ARM指令中常用作堆棧指針,但這只是一種習慣用法,用戶也可使用其他的寄存器作為堆棧指針。而在Thumb指令集中,某些指令強制性的要求使用R13作為堆棧指針。由于處理器的每種運行模式均有自己獨立的物理寄存器R13,在用戶應用程序的初始化部分,一般都要初始化每種模式下的R13,使其指向該運行模式的棧空間,這樣,當程序的運行進入異常模式時,可以將需要保護的寄存器放入R13所指向的堆棧,而當程序從異常模式返回時,則從對應的堆棧中恢復,采用這種方式可以保證異常發生后程序的正常執行。 4、有四種類型的堆棧: 堆棧是一種數據結構,按先進后出(First In Last Out,FILO)的方式工作,使用一個稱作堆棧指針的專用寄存器指示當前的操作位置,堆棧指針總是指向棧頂。 當堆棧指針指向最后壓入堆棧的數據時,稱為滿堆棧(Full Stack),而當堆棧指針指向下一個將要放入數據的空位置時,稱為空堆棧(Empty Stack)。 同時,根據堆棧的生成方式,又可以分為遞增堆棧(Ascending Stack)和遞減堆棧(DecendingStack),當堆棧由低地址向高地址生成時,稱為遞增堆棧,當堆棧由高地址向低地址生成時,稱為遞減堆棧。這樣就有四種類型的堆棧工作方式,ARM 微處理器支持這四種類型的堆棧工作方式,即: ◎ Full descending 滿遞減堆棧堆棧首部是高地址,堆棧向低地址增長。棧指針總是指向堆棧最后一個元素(最后一個元素是最后壓入的數據)。 ARM-Thumb過程調用標準和ARM、Thumb C/C++ 編譯器總是使用Full descending 類型堆棧。<這是什么原因呢?> 例子◎ Full ascending 滿遞增堆棧堆棧首部是低地址,堆棧向高地址增長。棧指針總是指向堆棧最后一個元素(最后一個元素是最后壓入的數據)。 ◎ Empty descending 空遞減堆棧堆棧首部是低(這里是不是錯了,應該是高地址吧)地址,堆棧向高地址增長。棧指針總是指向下一個將要放入數據的空位置。 ◎ Empty ascending 空遞增堆棧堆棧首部是高地址,堆棧向低地址增長。棧指針總是指向下一個將要放入數據的空位置。 5、操作堆棧的匯編指令堆棧類型 入棧指令 出棧指令 Full descending STMFD (STMDB) LDMFD (LDMIA) Full ascending STMFA (STMIB) LDMFA (LDMDA) Empty descending STMED (STMDA) LDMED (LDMIB) Empty ascending STMEA (STMIA) LDMEA (LDMDB) 例子: STMFD r13!, {r0-r5} ; Push onto a Full Descending Stack LDMFD r13!, {r0-r5} ; Pop from a Full Descending Stack. 1) 保護現場參數,不影響PC,嵌匯編的時候對之前的存參數的寄存器R0~R12保存 STMFD r13!, {r0-r7,LR} LDMFD r13!, {r0-r7,PC} 2) ARM匯編中lr(r14)寄存器的作用 lr(r14)的作用問題,這個lr一般來說有兩個作用: 1.當使用bl或者blx跳轉到子過程的時候,r14保存了返回地址,可以在調用過程結尾恢復。 2.異常中斷發生時,這個異常模式特定的物理R14被設置成該異常模式將要返回的地址。 另外注意pc,在調試的時候顯示的是當前指令地址,而用mov lr,pc的時候lr保存的是此指令向后數兩條指令的地址,大家可以試一下用mov pc,pc,結果得到的是跳轉兩條指令,這個原因是由于arm的流水線造成的,預取兩條指令的結果. 3.我們看到的LR值是上一個子程序調用保存的子程序返回地址,這個LR是要賦給PC的。 嵌入式匯編要手動保存返回地址,進行現場保護。 PC記錄當前運行的地址。下一條回自己+4 進入子程序,LR才自動更新為返回地址值,PC為程序運行地址 ARM匯編嵌套子程序幾個星期前閱讀了(加)Carl Hamacher、 Zvonko Vranesic、 Safwat Zaky編寫的《計算機組成》第五版中的ARM子程序調用的一些知識,啟發很大,順便將它整理了一下并加入了自己的理解。 子程序 1 通過寄存器傳遞參數 BL指令通常用于調用一個子程序。它和B指令的區別在于它將返回地址裝載到R14中。由于子程序可能是嵌套的,因此LR的內容必須保存在子程序所使用的堆棧中。 下面的例子使用寄存器傳遞參數。調用者通過寄存器R1和R2分別將數組的大小和數組的首地址傳遞給子程序;子程序利用寄存器R0將和傳遞給調用者。該子程序使用了寄存器R3,必須將它和LR推入堆棧。 調用程序 LDR R1, N LDR R2, POINTER BL LISTADD STR R0, SUM … 子程序 LISTADD STMFD R13!, {R3, R14} MOV R0, #0 LOOP LDR R3, [R2], #4 ADD R0, R0, R3 SUBS R1, R1, #1 BGT LOOP LDMFD R13!, {R3, R15} 注:這里并沒有遵守APCS(ARM過程調用標準),一般由調用者負責保存R0~R3,被調用者負責保存其他的寄存器以使調用返回后程序的狀態不被破壞。 2 通過堆棧傳遞參數 調用程序 LDR R0, POINTER STR R0, [R13, #-4]! ;將數組首地址推入堆棧 LDR R0, N STR R0, [R13, #-4]! ;將元素個數N推入堆棧 BL LISTADD LDR R0, [R13, #4] ;將元素和裝載到寄存器R0中 STR R0, SUM ADD R13, R13, #8 ;恢復堆棧 … 子程序 LISTADD STMFD R13!, {R0-R3, R14} LDR R1, [R13, #20] LDR R2, [R13, #24] MOV R0, #0 LOOP LDR R3, [R2], #4 ADD R0, R0, R3 SUBS R1, R1, #1 BGT LOOP STR R0, [R13,#24] ;把和推入堆棧的最深處 LDMFD R13!, {R0-R3, R15}
3 嵌套子程序 當子程序嵌套時,堆棧是用于處理返回地址的最合適的數據結構。當調用子程序時在堆棧上建立了完整的堆棧結構。應當注意當前子程序的堆棧幀指針所指向的空間中存儲的是調用當前子程序的子程序的堆棧幀指針。調用者將子程序所需要的參數按照順序推入堆棧。子程序首先保存工作寄存器、調用者的堆棧幀指針以及返回地址,然后它計算自己的堆棧幀指針的值(ADD FP, SP, #16),并利用這個堆棧幀指針從堆棧幀中獲取調用者傳遞給它的參數。在子程序完成它的任務之后,它也將返回值保存在堆棧中,此例保存在參數所在的內存單元。調用者和被調用者必須約定好參數的傳遞順序和返回值保存位置。如果返回值比較多的話,調用者要為返回值預先在堆棧中保留合適的空間。 調用程序 2000 LDR R0, PARAM2 STR R0, [SP, #-4]! ;將參數推入堆棧 LDR R0, PARAM1 STR R0, [SP, #-4]! BL SUB1 2020 LDR R0, [SP] ;保存SUB1的結果 STR R0, RESULT ADD SP, SP, #8 ;恢復堆棧 … 子程序 2100 SUB1 STMFD SP!, {R0-R3, FP,LR} ADD FP, SP, #16 ;計算幀指針 LDR R0, [FP, #8] ;載入參數1 LDR R1, [FP, #12] ;載入參數2 … LDR R2, PARAM3 ;載入參數3 STR R2, [SP, #-4]! ;將參數3推入堆棧 BL SUB2 2164 LDR R2, [SP], #4 ;將SUB2的結果彈出并存儲在R2中,并遞增SP … STR R3, [FP, #8] ;將結果推入堆棧 LDMFD SP!, {R0-R3, FP, PC} ;恢復寄存器并返回 3000 SUB2 STMFD SP!, {R0, R1, FP, LR} ADD FP, SP, #8 ;載入結構指針 LDR R0, [FP, #8] ;載入參數 … STR R1, [FP, #8] ;將結果推入堆棧 LDMFD SP!, {R0, R1, FP, PC}
3 子程序編譯后都放在哪 嵌匯編子程序:會在編譯后統一放到一個地方 系統調用__main()庫函數, 后再進入main.c前的初始化C下面的堆棧命令前(這個堆棧是用來放置C里參數的) 嵌匯編子程序:按定義順序放在堆棧前,而C語言子程序,放在初始化堆棧堆棧命令后。 |