對于嵌入式開發(fā)者來說,,了解匯編語言和內(nèi)核寄存器是對內(nèi)核深入理解的基礎(chǔ)。從開始寫起也沒想到內(nèi)容有這么多,,其中有很多干貨的東西,,希望自己能夠說明到了。其中有很多推薦的博文和網(wǎng)站,,在此要特別感謝韋東山老師的視頻,,絕對干貨滿滿! 本文目錄: 一,、ARM內(nèi)核寄存器 1.1 M3/M4內(nèi)核寄存器 1.2 A7內(nèi)核寄存器 1.3 ARM中的PC指針的值
二,、ARM匯編語言 2.1 ARM匯編基礎(chǔ) 2.2 匯編偽指令 2.3 ARM匯編指令集
三、代碼反匯編簡析 3.1 不同編譯器的反匯編 3.2 C和匯編比較分析
我們先來看幾個簡單的匯編指令:MOV R0,,R1 MOV PC,,R14
上面的指令中使用了匯編 MOV 指令,但是其中的 R0,,R1,,R14,PC分別是什么,?哪來的,?怎么用? 要講 ARM 匯編語言,,必須得先了解ARM的內(nèi)核寄存器,,內(nèi)核處理所有的指令計算,都需要用到內(nèi)核寄存器,,所以ARM匯編里面指令大都是基于寄存器的操作,。 ARM版本簡單介紹: 內(nèi)核(架構(gòu))版本 | 處理器版本 |
---|
ARMv1 | ARM1 | ARMv2 | ARM2、ARM3 | ARMv3 | ARM6,、 | ARMv4 | ARM7,、StrongARM | ARMv5 | ARM9、ARM10E | ARMv6 | ARM11 | ARMv7 | ARM Cortex-A,、ARM Cortex-M,、ARM Cortex-R | ARMv8 | ARM Cortex-A30,、ARM Cortex-A50、ARM Cortex-A70 |
一,、ARM內(nèi)核寄存器內(nèi)核寄存器與外設(shè)寄存器: 內(nèi)核寄存器與外設(shè)寄存器是完全不同的概念,。內(nèi)核寄存器是指 CPU 內(nèi)部的寄存器,CPU處理所有指令數(shù)據(jù)需要用到這些寄存器保存處理數(shù)據(jù),;外設(shè)寄存器是指的 串口,,SPI,GPIO口這些設(shè)備有關(guān)的寄存器,。 在我的另一篇博文:FreeRTOS記錄(三,、FreeRTOS任務(wù)調(diào)度原理解析_Systick、PendSV,、SVC)內(nèi)核中斷管理章節(jié)講到過Cortex-M的寄存器 的相關(guān)內(nèi)容,,這里我們再簡單說明一下: 1.1 M3/M4內(nèi)核寄存器R13,棧指針(Stack Pointer)- R13寄存器中存放的是棧頂指針,,M3/M4 的棧是向下生長的,,入棧的時候地址是往下減少的。
- 裸機(jī)程序不會用到PSP,,只用到MSP,,需要運行RTOS的時候才會用到PSP。
- 堆棧主要是通過POP,,PUSH指令來進(jìn)行操作,。在執(zhí)行 PUSH 和 POP 操作時, SP 的地址寄存器,,會自動調(diào)整,。
R14 ,連接寄存器(Link Register) LR 用于在調(diào)用子程序時存儲返回地址,。例如,,在使用 BL(分支并連接, Branch and Link)指令時,,就自動填充 LR 的值(執(zhí)行函數(shù)調(diào)用的下一指令),,進(jìn)而在函數(shù)退出時,正確返回并執(zhí)行下一指令,。如果函數(shù)中又調(diào)用了其他函數(shù),,那么LR將會被覆蓋,所以需要先將LR寄存器入棧,。 保存子程序返回地址,。使用BL或BLX時,跳轉(zhuǎn)指令自動把返回地址放入r14中;子程序通過把r14復(fù)制到PC來實現(xiàn)返回 當(dāng)異常發(fā)生時,,異常模式的r14用來保存異常返回地址,,將r14如棧可以處理嵌套中斷
R15,,程序計數(shù)器(Program Count) - 在Cortex-M3中指令是3級流水線,,出于對Thumb代碼的兼容的考慮,,讀取pc時,,會返回當(dāng)前指令地址+4的值。
- 讀 PC 時返回的值是當(dāng)前指令的地址+4,,關(guān)于M3,、M4 和 A7的 PC值的問題需要單獨來解釋一下。
程序狀態(tài)寄存器,,該寄存器由三個程序狀態(tài)寄存器組成
應(yīng)用PSR(APSR) :包含前一條指令執(zhí)行后的條件標(biāo)志,,比較結(jié)果:大于等于,小于,,進(jìn)位等等,;中斷PSR(IPSR ) :包含當(dāng)前ISR的異常編號
執(zhí)行PSR(EPSR) :包含Thumb狀態(tài)位1.2 A7內(nèi)核寄存器(上圖取自原子教材,此圖在官方文檔《ARM Cortex-A(armV7)編程手冊V4.0》中第3章.ARM Processor Modes and Registers 部分有英文原版,,這里用中文版本更容易理解)A7的 R13,、R14、R15 的作用和 M3/4類似,。 需要注意的一點就是,,對于A7而言**R15,程序計數(shù)器(Program Count)**: - 讀 PC 時返回的值是當(dāng)前指令的地址+8,, PC 指向當(dāng)前指令的下兩條指令地址,。
- 由于ARM指令總是以字對齊的,故PC寄存器 bit[1:0] 總是00,。
A7內(nèi)核的程序狀態(tài)寄存器 CPSR:
1.3 ARM中的PC指針的值因為ARM指令采用三級流水線機(jī)制,,所以PC指針的值并不是當(dāng)前執(zhí)行的指令的地址值: - 同時已經(jīng)在對下一條指令進(jìn)行譯碼,,
- 同時已經(jīng)在讀取下下一條指令:PC = A +4 (Thumb/Thumb2指令集),、PC = A + 8 (ARM指令集)
在文檔《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition》中對于 PC 的值有明確的說明: M3/M4/M0:
PC的值 = 當(dāng)前地址 + 4; 下面是一個 STM32F103 反匯編程序,,找了一段有[pc,,#0]的代碼,方便判斷:二,、ARM匯編語言ARM芯片屬于精簡指令集計算機(jī)(RISC:Reduced Instruction Set Computing),具體說明在下面這篇博文5.4小結(jié)有過說明: STM32的內(nèi)存管理相關(guān)(內(nèi)存架構(gòu),內(nèi)存管理,,map文件分析)2.1 ARM匯編基礎(chǔ)2.1.1 ARM指令集說明最初,,ARM公司發(fā)布了兩類指令集: - ARM指令集,32位的ARM指令,,每條指令占據(jù)32位,,高效,但是太占空間,;
- Thumb指令集,,16位的Thumb指令,每條指令占據(jù)16位,,節(jié)省空間,;
比如:MOV R0,R1 這條指令,,可能是16位的,,也可能是32位的 那么,在匯編中是如何在 ARM 指令 和 Thumb 指令之間切換呢,? /*ARM指令 與 Thumb 指令 的切換*/
CODE16 ;(表示下面是 Thumb 指令) ... ...
;(調(diào)用下面的B函數(shù)) bx B_addr;(B的地址B_addr的bit0 = 0,,表示跳轉(zhuǎn)過去執(zhí)行 ARM 指令) ;A 函數(shù) ...
CODE32 ;(表示下面是 ARM 指令) ... ... ;B 函數(shù) ;(回到上面的A函數(shù)) bx A_addr + 1 ;(A的地址A_addr的bit0 = 1,表示跳轉(zhuǎn)過去執(zhí)行 Thumb 指令) ...
/**********************/
對于A7,、ARM7,、ARM9 內(nèi)核而言它們支持 16位的Thumb 指令集 和 32位的 ARM 指令集。 對于M3,、M4 內(nèi)核而言它們支持的是 Thumb2 指令集,,它支持16位、32位指令混合編程,。 對于內(nèi)核來說使用的是 ARM指令集 還是 Thumb指令集,,就是在 XPSR 和 CPSR。 在M3/M4中,, XPSR 寄存器的 T(bit24):1表示 Thumb指令集,。
根據(jù)上面所述,M3是使用的 Thumb2 指令集,,所以會有 T 總是 1,。 在A7中 CPSR中的:T(bit5) :控制指令執(zhí)行狀態(tài),表明本指令是 ARM 指令還是 Thumb 指令,,通常和 J(bit24)一起表明指令類型,。
J(bit24) | T(bit5) | 指令集 |
---|
0 | 0 | ARM | 0 | 1 | Thumb | 1 | 1 | ThumbEE -- 提供從Thumb-2而來的一些擴(kuò)充性,在所處的運行環(huán)境下,,使得指令集能特別適用于運行階段的編碼產(chǎn)生(例如實時編譯),。Thumb-2EE是專為一些語言如Limbo,、Java、C#,、Perl和Python,,并能讓實時編譯器能夠輸出更小的編譯碼卻不會影響到性能。 | 1 | 0 | Jazelle |
回到開始的指令 MOV R0,,R1 code 16 ;(表示下面指令是16位的 Thumb 指令) MOV R0,,R1 code 32 ;(表示下面指令是32位的 ARM 指令) MOV R0,R1 Thumb ;(編譯器會根據(jù)指令自動識別是32位還是16位的 Thumb2) MOV R0,,R1
2.1.2 ARM匯編格式編碼格式: 不同指令集的編碼格式(以 LDR 為例),,摘自《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition》:
以“數(shù)據(jù)處理”(其他的還有內(nèi)存訪問,分支跳轉(zhuǎn)等)指令為例,,UAL匯編格式為:
Operation 表示各類匯編指令,,比如 ADD,、MOV,;cond表示conditon,即該指令執(zhí)行的條件,,如 EQ,,NE 等;S表示該指令執(zhí)行后,,是否會影響CPSR寄存器的值,, 是否影響CPSR 寄存器的值,書寫時影響CPSR,,否則不影響,;Rd 為目的寄存器,用來存儲運算的結(jié)果,;Rn第一個操作數(shù)的寄存器Operand2第二個操作數(shù) ,,其可以有3種操作源:1-- 立即數(shù)
2-- 寄存器
3-- 寄存器移位 其指令編碼格式如下(32位):|bit 31-28 |27-25 |24-21 |20 |19-16 | 15-12 |11-0 |
|--|--|--|--|--|--|--|--|--|
|cond | 001 |Operation |S |Rn |Rd | Operand2 | 舉個例子: ... CMP R0,R2 ;比較R0和R2的值 MOV EQ R0,,R1 ;加上EQ,,如果上面R0的值和R2的值相等的話,才執(zhí)行此語句 ...
對于“數(shù)據(jù)處理”處理指令中的Operation,,指令集如下:
2.1.3 立即數(shù)在一條ARM數(shù)據(jù)處理指令中,除了要包含處理的數(shù)據(jù)值外,,還要標(biāo)識ARM命令名稱,,控制位,寄存器等其他信息,。這樣在一條ARM數(shù)據(jù)處理指令中,,能用于表示要處理的數(shù)據(jù)值的位數(shù)只能小于32位; 在上面的ARM匯編格式中我們介紹過,ARM在指令格式中設(shè)定,,只能用指令機(jī)器碼32位中的低12位來表示要操作的常數(shù),。
那么,對于指令MOV R0, #value (把value的值存入R0寄存器)而言,,value 的值也不能是任意的值,,其值只能是符合某些規(guī)定的數(shù),在官方文檔中 value 的值需要滿足如下條件:
什么是立即數(shù),? 滿足上圖中條件的數(shù)我們稱之為 立即數(shù),,立即數(shù)就是符合一定規(guī)矩的數(shù)。 立即數(shù)表示方式:每個立即數(shù)由一個8位的常數(shù)循環(huán)右移偶數(shù)位得到,。其中循環(huán)右移的位數(shù)由一個4位二進(jìn)制的兩倍表示,。 立即數(shù) = 一個8位的常數(shù) 循環(huán)位移 偶數(shù)位 一個8bit常數(shù)循環(huán)右移(Y*2 = {0,2,4,6,8, ...,26, 28, 30})就得到一個立即數(shù)了;(為什么是0到30的偶數(shù)下面解釋)。 如果需要深入理解立即數(shù),,推薦一篇博文:深刻認(rèn)識 -->> 立即數(shù) ARM處理器是按32位來處理數(shù)據(jù)的,,ARM處理器處理的數(shù)據(jù)是32位,為了擴(kuò)展到32位,,因此使用了構(gòu)造的方法,,在12位中用8位表示基本數(shù)據(jù)值,用4位表示位移值,,通過用8位基本數(shù)據(jù)值往右循環(huán)移動4位位移值*2次,,來表示要操作的常數(shù)。 這里要強(qiáng)調(diào)最終的循環(huán)次數(shù)是4位位移值乘以2得到的,,所以得到的最終循環(huán)次數(shù)肯定是一個偶數(shù),,為什么要乘以2呢,實質(zhì)還是因為范圍不夠,,4位表示位移次數(shù),,最大才15次(移位0,等于沒有循環(huán)),,加上8位數(shù)據(jù)還是不夠32位,,這樣只能通過ALU的內(nèi)部結(jié)構(gòu)設(shè)計將4位位移次數(shù)乘以2,這樣就能用12位表示32位常數(shù)了,。 所以 12bit 數(shù)據(jù)存放格式如下:|bit 11-8 |7-0 |
|--|--|--|--|--|--|--|--|--|
|移位 1111b (0~15) | 8bit常數(shù) | 但是我們?nèi)ヅ袛嘁粋€數(shù)是否立即數(shù),,實在是太麻煩了,但是我們想把任意數(shù)值賦給 R0 寄存器,,怎么辦,? 這就需要用到偽指令了,下面說一說什么是偽指令,。 2.2 匯編偽指令匯編語言分成兩塊:標(biāo)準(zhǔn)指令集和非標(biāo)準(zhǔn)指令集,。偽指令屬于非標(biāo)準(zhǔn)指令集,。 什么是偽指令? 類似于宏的東西,,把復(fù)雜的有好幾天指令進(jìn)行跳轉(zhuǎn)的完成的小功能級進(jìn)行新的標(biāo)簽設(shè)定,,這就是偽指令。 類似于學(xué)c語言的時候的預(yù)處理,,在預(yù)處理的時候把它定義于一堆的宏轉(zhuǎn)化為真正的c語言的代碼,。同樣,偽指令是在定義好之后的匯編,,匯編的時候會把它翻譯成標(biāo)準(zhǔn)指令,,也許一條簡單的偽指令可以翻譯成很多條標(biāo)準(zhǔn)的匯編指令集,所以這就是偽指令最重要的作用,。 我們前面說的 CODE16 CODE32 也是偽指令,,用來指定其后的代碼格式。 偽指令的作用,? 基本的指令可以做各類操作了,,但操作起來太麻煩了。偽指令定義了一些類似于帶參數(shù)的宏,,能夠更好的實現(xiàn)匯編程序邏輯,。(比如我現(xiàn)在要設(shè)置一個值給寄存器R0,,但下次我修改了寄存器R0之后又需要讀出來剛才的值,,那我們就要先臨時保存值到SPSR,CPSR,然后不斷切換,。) 偽指令只是在匯編器之前作用,,匯編以后翻譯為標(biāo)準(zhǔn)的匯編令集。 偽指令的類別偽指令可分為ARM匯編偽指令和GNU匯編偽指令,。 ARM匯編偽指令是ARM公司的,,GNU匯編偽指令是GNU平臺的。他們有自己的匯編器,,不同的匯編器的解釋語法可以設(shè)成不同,。
2.2.1 GNU匯編偽指令這里列出部分偽指令說明,具體的偽指令可以結(jié)合 ARM匯編偽指令分析:bit 11-8 | 7-0 |
---|
.word | 分配一個4字節(jié)空間 | .byte | 定義單字節(jié)數(shù)據(jù) | .short | 定義雙字節(jié)數(shù)據(jù) | .long | 定義一個4字節(jié)數(shù)據(jù) | .equ | 賦值語句:.equ a, 0x11 | .align | 數(shù)據(jù)字節(jié)對齊:.align 4 (4字節(jié)對齊) | .global | 定義全局符號:.global Default_Handler | .end | 源文件結(jié)束 |
2.2.2 ARM匯編偽指令在我的另一篇博文:STM32的啟動過程(startup_xxxx.s文件解析) 里面有過一些對偽指令意思的的說明,,下面也列出部分說明: AREA: 用于定義一個代碼段或數(shù)據(jù)段,。屬性字段表示該代碼段(或數(shù)據(jù)段)的相關(guān)屬性,多個屬性用逗號分隔,。其中,,段名若以數(shù)字開頭,則該段名需用?“?|?”?括起來: ALIGN?偽指令可通過添加填充字節(jié)的方式,,使當(dāng)前位置滿足一定的對其方式,。其中,,表達(dá)式的值用于指定對齊方式,可能的取值為2的冪,,如?1?,、2?、4?,、8?,、16?等。 若未指定表達(dá)式,,則將當(dāng)前位置對齊到下一個字的位置,。指定其后面的指令為 ARM 指令還是?Thumb?指令,前面介紹過,。 ENTRY: 用于指定匯編程序的入口點,。在一個完整的匯編程序中至少要有一個?ENTRY?(也可以有多個,當(dāng)有多個?ENTRY?時,,程序的真正入口點由鏈接器指定),,但在一個源文件里最多只能有一個?ENTRY。 在startup_stm32f103xg.s 里面就沒有,。 END: 用于通知編譯器已經(jīng)到了源程序的結(jié)尾,。IMPORT 和 EXPORT:IMPORT 定義表示這是一個外部變量的標(biāo)號,不是在本程序定義的
EXPORT 表示本程序里面用到的變量提供給其他模塊調(diào)用的,。2.2.3 LDR 和 ADR LDR 偽指令:
簡單介紹了偽指令基礎(chǔ),,回到上一小結(jié)留下的問題,想要把任意值復(fù)制給 R0,,怎么處理,,我們使用偽指令:LDR R0, =value 編譯器會把“偽指令”替換成真實的指令: LDR R0, =0x12 0x12是立即數(shù),那么替換為:MOV R0, #0x12
LDR R0, =0x12345678 0x12345678不是立即數(shù),,那么替換為:LDR R0, [PC, #offset] // 2. 使用Load Register讀內(nèi)存指令讀出值,,offset是鏈接程序時確定的
……Label DCD 0x12345678 // 1. 編譯器在程序某個地方保存有這個值
ADR 偽指令:
ADR的意思是:address,用來讀某個標(biāo)號的地址:ADR{cond} Rd, labe1 ADR R0, Loop ... Loop ADD R0, R0, #1
;(它是“偽指令”,,會被轉(zhuǎn)換成某條真實的指令,,比如:) ADD R0, PC, #val ; loop的地址等于PC值加上或者減去val的值,val的值在鏈接時確定, ... Loop ADD R0, R0, #1
2.3 ARM匯編指令集在《ARM Cortex-M3與Cortex-M4權(quán)威指南》一文中第5章節(jié)有詳細(xì)的指令集說明:匯編指令可以分為幾大類:數(shù)據(jù)處理,、內(nèi)存訪問,、跳轉(zhuǎn)、飽和運算,、其他指令,。數(shù)據(jù)傳輸命令 MOVMOV指令,用于將數(shù)據(jù)從一個寄存器拷貝到另外一個寄存器,,或者將一個立即數(shù)傳遞到寄存器,。 MOV指令的格式為:MOV{條件}{S} 目的寄存器,,源操作數(shù)。 MOV R0,,R1 ;@將寄存器R1中的數(shù)據(jù)傳遞給R0,,即R0=R1 MOV R0, #0X12 ;@將立即數(shù)0X12傳遞給R0寄存器,即R0=0X12
狀態(tài)寄存器訪問 MRS 和 MSRMRS指令,,用于將特殊寄存器(如CPSR和SPSR)中的數(shù)據(jù)傳遞給通用寄存器,。 MSR指令,和MRS相反,,用來將普通寄存器的數(shù)據(jù)傳遞給特殊寄存器,。 ;M3/M4 MRS R0, APSR ;單獨讀APSR MRS R0, PSR ; 讀組合程序狀態(tài)
;A7 MRS R0, CPSR ; 讀組合程序狀態(tài)
... MSR CPSR,R0 ;傳送R0的內(nèi)容到CPSR
存儲器訪問 LDR 和 STRLDR: LDR 指令用于從存儲器中將一個32位的字?jǐn)?shù)據(jù)傳送到目的寄存器中。該指令通常用于從存儲器中讀取32位的字?jǐn)?shù)據(jù)到通用寄存器,,然后對數(shù)據(jù)進(jìn)行處理,。 指令的格式為:LDR{條件} 目的寄存器,<存儲器地址> 當(dāng)程序計數(shù)器PC作為目的寄存器時,,指令從存儲器中讀取的字?jǐn)?shù)據(jù)被當(dāng)作目的地址,,從而可以實現(xiàn)程序流程的跳轉(zhuǎn)。 LDRB: 字節(jié)操作 LDRH: 半字操作 LDR Rd, [Rn , #offset] ;從存儲器Rn+offset的位置讀取數(shù)據(jù)存放到Rd中,。 ... LDR R0, =0X02077004 ;偽指令,,將寄存器地址 0X02077004 加載到 R0 中,即 R0=0X02077004 LDR R1, [R0] ;讀取地址 0X02077004 中的數(shù)據(jù)到 R1 寄存器中 ... LDR R0,[R1,,R2] ;將存儲器地址為R1+R2的字?jǐn)?shù)據(jù)讀入寄存器R0,。 LDR R0,[R1,#8] ;將存儲器地址為R1+8的字?jǐn)?shù)據(jù)讀入寄存器R0,。 ... LDR R0,[R1,,R2,,LSL#2]! ;將存儲器地址R1+R2×4的字?jǐn)?shù)據(jù)讀入寄存器R0,并將新地址R1+R2×4寫入R1,。 LDR R0,[R1],,R2,LSL#2 ;將存儲器地址R1的字?jǐn)?shù)據(jù)讀入寄存器R0,,并將新地址R1+R2×4寫入R1,。 ... LDRH R0,[R1] ;將存儲器地址為R1的半字?jǐn)?shù)據(jù)讀入寄存器R0,,并將R0的高16位清零,。
STR 指令用于從源寄存器中將一個32位的字?jǐn)?shù)據(jù)傳送到存儲器中。該指令在程序設(shè)計中比較常用,,且尋址方式靈活多樣,,使用方式可參考指令LDR,。 指令的格式為:STR{條件} 源寄存器,<存儲器地址> STRB: 字節(jié)操作,,從源寄存器中將一個8位的字節(jié)數(shù)據(jù)傳送到存儲器中,。該字節(jié)數(shù)據(jù)為源寄存器中的低8位。 STRH: 半字操作,,從源寄存器中將一個16位的半字?jǐn)?shù)據(jù)傳送到存儲器中,。該半字?jǐn)?shù)據(jù)為源寄存器中的低16位。STR Rd, [Rn, #offset] ;將Rd中的數(shù)據(jù)寫入到存儲器中的Rn+offset位置,。 ... LDR R0, =0X02077004 ;將寄存器地址 0X02077004 加載到 R0 中,,即 R0=0X02077004 LDR R1, =0X2000060c ;R1 保存要寫入到寄存器的值,即 R1=0X2000060c STR R1, [R0] ;將 R1 中的值寫入到 R0 中所保存的地址中 ... STR R0,,[R1],#8 ;將R0中的字?jǐn)?shù)據(jù)寫入以R1為地址的存儲器中,,并將新地址R1+8寫入R1。 STR R0,,[R1,#8] ;將R0中的字?jǐn)?shù)據(jù)寫入以R1+8為地址的存儲器中,。 ...
壓棧和出棧 PUSH 和 POPPUSH : 壓棧,將寄存器中的內(nèi)容,,保存到堆棧指針指向的內(nèi)存上面,,將寄存器列表存入棧中。 PUSH < reg list > POP : 出棧,,從棧中恢復(fù)寄存器列表 POP < reg list > push {R0, R1} ;保存R0,R1 push {R0~R3,R12} ;保存 R0~R3 和 R12,,入棧 pop {R0~R3} ;恢復(fù)R0 到 R3 ,出棧
以M3內(nèi)核來舉個例子: 假設(shè)當(dāng)前 MSP 值為 0x2000 2480,;寄存器 R0 的值為 0x3434 3434
寄存器 R1 的值為 0x0000 1212
寄存器 R2 的值為 0x0000 0000 執(zhí)行push {R0, R1,,R2} 之后, 內(nèi)存地址的數(shù)據(jù)為:0x2000 2474的值為: 0x3434 3434 (R0的值)
0x2000 2478的值為: 0x0000 1212 (R1的值)
0x2000 247C的值為: 0x0000 0000 (R2的值)
MSP 的值變成 0x2000 2474 高位寄存器保存到高地址,,先入棧,,如果是POP,數(shù)據(jù)先出到低位寄存器,。 跳轉(zhuǎn)指令 B 和 BLB : ARM 處理器將立即跳轉(zhuǎn)到指定的目標(biāo)地址,,不再返回原地址。 B指令的格式為:B{條件} 目標(biāo)地址 注意,,存儲在跳轉(zhuǎn)指令中的實際值是相對當(dāng)前PC值的一個偏移量,,而不是一個絕對地址,它的值由匯編器來計算,。 //設(shè)置棧頂指針后跳轉(zhuǎn)到C語言 _start: ldr sp,=0X80200000 ;設(shè)置棧指針 b main ;跳到 main 函數(shù)
BL : BL 跳轉(zhuǎn)指令,,在跳轉(zhuǎn)之前會在寄存器LR(R14)中保存當(dāng)前PC寄存器值,所以可以通過將LR 寄存器中的值重新加載到PC中來繼續(xù)從跳轉(zhuǎn)之前的代碼處運行,,是子程序調(diào)用的常用的方法,。 BL loop ;跳轉(zhuǎn)到標(biāo)號loop處執(zhí)行時,,同時將當(dāng)前的PC值保存到R14中
BLX: 該跳轉(zhuǎn)指令是當(dāng)子程序使用Thumb指令集,而調(diào)用者使用ARM指令集時使用,。 BLX指令從ARM指令集跳轉(zhuǎn)到指令中所指定的目標(biāo)地址,,并將處理器的工作狀態(tài)有ARM狀態(tài)切換到Thumb狀態(tài),該指令同時將PC的當(dāng)前內(nèi)容保存到寄存器R14中,。 BX: BX指令跳轉(zhuǎn)到指令中所指定的目標(biāo)地址,,目標(biāo)地址處的指令既可以是ARM指令,也可以是Thumb指令,。 算數(shù)運算指令算數(shù)運算指令和下面的邏輯運算指令表格摘自《【正點原子】I.MX6U嵌入式Linux驅(qū)動開發(fā)指南》,。 邏輯運算指令
三、代碼反匯編簡析- 匯編
匯編文件轉(zhuǎn)換為目標(biāo)文件(里面是機(jī)器碼,,機(jī)器碼是給CPU使用的,,燒錄保存在Flash空間的就是機(jī)器碼)。
- 反匯編
可執(zhí)行文件(目標(biāo)文件,,里面是機(jī)器碼),,轉(zhuǎn)換為匯編文件。
3.1 不同編譯器的反匯編3.1.1 Keil下面生成反匯編文件fromelf –text -a -c –output=(改成你想生成的反匯編名字一般是工程名字).dis (需要的axf文件,,根據(jù)你工程生成axf的路徑填寫).axf
設(shè)置好以后編譯之后就會生成反匯編.dis文件:
打開如下所示:
對于上圖中的紅色圈出來的語句,,我們可以根據(jù)本文 第 二 章節(jié)的第2小節(jié) ARM匯編格式中的介紹來分析一下:
簡單分析如下(立即數(shù)就不分析了= =!):
3.1.2 gcc下生成反匯編文件在X86架構(gòu)下的電腦上生成ARM架構(gòu)的匯編代碼有兩種方式: - 使用交叉編譯工具鏈 指定-S選項可以生成匯編中間文件,。ex:gcc -S test.c
- 使用 objdump 反匯編 arm二進(jìn)制文件,。
上述兩種方法的區(qū)別為: (1)反匯編可以生成ARM指令操作碼,-S生成的匯編沒有指令碼
(2)反匯編的代碼是經(jīng)過編譯器優(yōu)化過的,。(3)反匯編代碼量很大,。 對于ARM Cortex-M,使用的是 arm-none-eabi-objdump,,常用指令如下: - arm-none-eabi-objdump -d -S(可省) a1.o 查看a1.o反匯編可執(zhí)行段代碼
- arm-none-eabi-objdump -D -S(可省) a1.o 查看a1.o反匯編所有段代碼
- arm-none-eabi-objdump -D -b binary -m arm ab.bin 查看ab.bin反匯編所有代碼段
對于使用 arm-none-eabi-gcc 工具鏈(以STM32CUbeMX)的內(nèi)核來說,,使用如下方式生成反匯編文件: $(OBJDUMP) -D -b binary -m arm (需要的elf文件,一般是工程名字).elf > (改成你想生成的反匯編名字,,一般是工程名字).dis # OBJDUMP = arm-none-eabi-objdump -D表示對全部文件進(jìn)行反匯編,,-b表示二進(jìn)制,-m表示指令集架構(gòu) Makefile修改如下: ... TARGET = D6TPir ####################################### # paths ####################################### # Build path BUILD_DIR = build ... PREFIX = arm-none-eabi- ... OBJDUMP = $(PREFIX)objdump
dis: $(OBJDUMP) -D -b binary -m arm $(BUILD_DIR)/$(TARGET).elf > $(BUILD_DIR)/$(TARGET).dis # $(OBJDUMP) -D -b binary -m arm $(BUILD_DIR)/$(TARGET).bin > $(BUILD_DIR)/$(TARGET).dis
執(zhí)行 make dis 即可生成 .dis 文件:
打開文件查看,,發(fā)現(xiàn)怎么這個匯編語言有點不一樣:
經(jīng)過研究了一段時間,加上了-M force-thumb 后稍微有點樣子了:
在網(wǎng)上有各種參考,,但是我都測試過了,,并沒有找到合適的生成完全和標(biāo)準(zhǔn)匯編一致的那種,-M后面的參數(shù)也不能亂加,,需要根據(jù)自己的交叉編譯器,,因為這里用的是 arm-none-eabi-gcc,,所以可以通過arm-none-eabi-objdump --help 查看能用的命令和參數(shù):
gcc工具鏈下的匯編還是不太熟悉,所以我們下面反匯編文件與 C語言的對比,,使用Keil下的反匯編進(jìn)行說明,。 3.2 C和匯編比較分析前面介紹了那么多,最終用一個簡單的程序?qū)Ρ纫幌翪語言反匯編后的匯編語言,,加深一下印象,,當(dāng)作個實戰(zhàn)總結(jié)。 基于STM32L051(Cortex-M0)內(nèi)核,,目的是為了比較C和匯編,,用了個最簡單的程序來分析,沒有用到任務(wù)外設(shè),,程序如下: //前面省略... void delay(u32 count) { while(count--); }
u32 add(u16 val1,u16 val2) { u32 add_val;
add_val = val1 + val2;
return add_val; } int main(void) { u16 a,b; u32 c; a = 12345; b = 45678; c = add(a,b); while(1) { c--; delay(200000); } }
反匯編的代碼對應(yīng)部分如下(因為基于硬件平臺,,其他異常中斷,堆,,棧,,包括其他一些也有匯編代碼,這里省略): ;省略前面 delay 0x080001ae: bf00 .. NOP 0x080001b0: 1e01 .. SUBS r1,r0,#0 0x080001b2: f1a00001 .... SUB r0,r0,#1 0x080001b6: d1fb .. BNE 0x80001b0 ; delay + 2 0x080001b8: 4770 pG BX lr add 0x080001ba: 4602 .F MOV r2,r0 0x080001bc: 1850 P. ADDS r0,r2,r1 0x080001be: 4770 pG BX lr main 0x080001c0: f2430439 C.9. MOV r4,#0x3039 0x080001c4: f24b256e K.n% MOV r5,#0xb26e 0x080001c8: 4629 )F MOV r1,r5 0x080001ca: 4620 F MOV r0,r4 0x080001cc: f7fffff5 .... BL add ; 0x80001ba 0x080001d0: 4606 .F MOV r6,r0 0x080001d2: e003 .. B 0x80001dc ; main + 28 0x080001d4: 1e76 v. SUBS r6,r6,#1 0x080001d6: 4804 .H LDR r0,[pc,#16] ; [0x80001e8] = 0x30d40 0x080001d8: f7ffffe9 .... BL delay ; 0x80001ae 0x080001dc: e7fa .. B 0x80001d4 ; main + 20 $d 0x080001de: 0000 .. DCW 0 0x080001e0: e000ed0c .... DCD 3758157068 0x080001e4: 05fa0000 .... DCD 100270080 0x080001e8: 00030d40 @... DCD 200000 ;省略后面
3.2.1 MOV后面 立即數(shù)的疑問在對比分析這段代碼前,,在 main 函數(shù)中的第一句: 0x080001c0: f2430439 C.9. MOV r4,#0x3039
就有一個大大的疑問,, MOV r4,#0x3039 中 0x3039 并不是立即數(shù)(按照我們第二章 立即數(shù)的說明) ,包括接下來的 0xb26e 也不是立即數(shù),,怎么可以直接用 mov,,按理來說需要用 LDR偽指令的?,?至于這個問題,,網(wǎng)上簡單查找了一下,找到一篇有關(guān)說明的文章:ARM 匯編的mov操作立即數(shù)的疑問 其中有說到,,在 keil 公司方網(wǎng)站里關(guān)于arm匯編的說明里有這么一段: Syntax
MOV{cond} Rd, #imm16
where: imm16 is any value in the range 0-65535. 所以,,是不是在 Keil 中的arm匯編 立即數(shù)可以使16位的? 為了驗證一下,,我稍微修改了一下程序,,就是把a(bǔ)的值賦值超過16位(當(dāng)然定義函數(shù)之類的也要跟著改,測試代碼中a為u16的無符號整形),,測試了一下,。 a賦值為 65535,結(jié)果如下(65535不是立即數(shù),,也可以直接mov): 0x080001c0: f64f75ff O..u MOV r5,#0xffff
a賦值為 65536,,結(jié)果如下(65536是立即數(shù),可以直接mov):0x080001c0: f44f3580 O..5 MOV r5,#0x10000
a賦值為一個大于16位的,不是立即數(shù)的數(shù),,比如:0x1FFFF :0x080001c0: 4d08 .M LDR r5,[pc,#32] ; [0x80001e4] = 0x1ffff
果然,,最后當(dāng) a 大于16位,不是立即數(shù)時候,,會使用偽指令 LDR,,所以我們可以得出結(jié)論: 在 Keil 中的arm匯編中,16位內(nèi)(包括16位)的數(shù)都直接使用 MOV 賦值,,大于16位,,如果是立即數(shù),直接使用MOV,,不是立即數(shù)用LDR (立即數(shù)的判斷方式還是前面講的那樣),。 3.2.2 反匯編文件解析對于上面的示例程序的匯編碼,簡單解析如下: 添加一個有意思的測試對于delay 函數(shù)中的語句,,上圖是while(count--); 改成while(--count); 后匯編代碼如下: 對于上面的測試程序,,匯編中并沒有使用到 PUSH 和 POP 指令,因為程序太簡單了,,不需要使用到棧,,為了能夠熟悉下單片機(jī)中必須且經(jīng)常需要用到的 棧,,我們稍微修改一下add 函數(shù),,在add函數(shù)中調(diào)用了delay 函數(shù): u32 add(u16 val1,u16 val2) { u32 add_val;
add_val = val1 + val2;
delay(10);
return add_val; }
對于的add函數(shù)匯編代碼如下: add 0x080001ba: b530 0. PUSH {r4,r5,lr} ;把r4 r5 lr的值入棧 0x080001bc: 4603 .F MOV r3,r0 0x080001be: 460c .F MOV r4,r1 0x080001c0: 191d .. ADDS r5,r3,r4 0x080001c2: 200a . MOVS r0,#0xa 0x080001c4: f7fffff3 .... BL delay ; 0x80001ae 0x080001c8: 4628 (F MOV r0,r5 0x080001ca: bd30 0. POP {r4,r5,pc} ;把r4 r5 lr的值出棧,
匯編中可以看到,,指令后面后面加了個S ,,MOVS ,、ADDS,這就是我們前面說到的,,帶了S 會影響 xPSR 寄存器中的值,。可以看到,因為存在函數(shù)的多次調(diào)用,,main 函數(shù)中調(diào)用add 函數(shù),,add 函數(shù)中調(diào)用delay 函數(shù),所以在add函數(shù)運行之前,,通過 push 把 r4,r5,lr 寄存器的值先存入棧中,,等待程序執(zhí)行完(函數(shù)調(diào)用結(jié)束)再吧 r4,r5,lr 寄存器的值恢復(fù)。 上面的程序雖然簡單,,但是通過我們C程序 與 匯編程序的對比分析,,能夠讓我們更加深入的理解匯編語言。 版權(quán)歸原作者所有,,如有侵權(quán),,請聯(lián)系刪除,。
|