ARM彙編中PC暫存器詳解

2021-09-26 06:53:39 字數 3810 閱讀 8788

近日,在研究一些開源native層hook方案的實現方式,並據此對arm彙編層中容易出問題的一些地方做了整理,以便後來人能有從中有所收穫並應用於現實問題中。當然,文中許多介紹參考了許多零散的文章,本文重點工作在於對相關概念的整理收集,並按相對合理順序引出後文中對hook技術中的一些難點的解讀。       

android平台大多採用了arm架構的cpu,而arm屬risc,與x86架構的處理器有不同的特徵,本文講介紹arm中不容易理解的pc暫存器各種問題,包括arm流水線、pc暫存器指向問題、arm和thumb指令的區分及相關概念在native層hook中的應用問題。

1.arm暫存器介紹

arm微處理器共有37個32位暫存器,其中31個為通用暫存器,6個為狀態暫存器。但是這些暫存器不能被同時訪問,具體哪些暫存器是可以訪問的,取決arm處理器的工作狀態及具體的執行模式。但在任何時候,通用暫存器r14~r0、程式計數器pc、乙個狀態暫存器都是可訪問的。

簡言之,在使用者模式下arm可見的暫存器有16個32位的暫存器(r0到r15)和乙個當前程式狀態暫存器cpsr,其中r15是程式計數器pc,r14用於儲存子程式的返回位址lr,r13用於儲存堆疊棧頂sp。

本文我們將著重介紹一下r15暫存器,即pc暫存器。理論上,pc暫存器應指向即將執行的下一條指令的位址,然而在實際應用中卻發現pc暫存器總不是如此。經過翻看資料發現,該問題是由於arm存在的指令流水線導致的。

2.arm流水線介紹

流水線技術通過多個功能部件並行工作來縮短程式執行時間,提高處理器核的效率和吞吐率,從而成為微處理器設計中最為重要的技術之一。也就是說,通過劃分指令執行過程中的不同階段,並通過並行執行,從而提高指令的執行效率。arm7處理器採用了**流水線結構,包括取指(fetch)、解碼(decode)、執行(execute)**。  

3.pc暫存器的指向問題

如上圖所示,在執行add r0, r1, #5指令時,第二條指令正在解碼階段,而第三條指令正在取指階段。在執行第一條指令時,pc暫存器應指向第三條指令。也即,當處理器為**流水線結構時,pc暫存器總是指向隨後的第三條指令。 

當處理器處於arm狀態時,每條arm指令為4個位元組,所以pc暫存器的值為當前指令位址 + 8位元組

當處理器處於thumb狀態時,每條thumb指令為2位元組,所以pc暫存器的值為當前指令位址 + 4位元組

此外,在arm9中,採用了五級流水線結構,是在arm7的**流水線結構後面新增了兩個新的過程。因此,指令的執行過程和取指過程還是相隔乙個解碼過程,因而pc還是指向當前指令隨後的第三條指令。 

另外,關於pc暫存器需要注意的一點是:當使用指令str或stm對r15進行儲存時,儲存的可能是當前指令位址加8或當前指令位址加12。具體是加8還是加12,取決於具體的處理器設計。但是,同乙個晶元只能是其中一種的方案,即只能是加8或加12中的一種。

可以通過如下的**確定處理器採用的那種方式: 

sub r1,pc, #4    ;r1中存放str指令位址 

str pc,[r0]      ;用str指令將pc儲存到r0指向的位址單元中,pc=str指令位址+偏移量(偏移量為8或者12)。 

ldr r0,[r0]    ;讀取str指令位址+偏移量的值 

sub r0,r0,r1    ; str指令位址+偏移量的值減去str指令的位址,得到偏移量值(8或者12)。 

4. arm/thumb指令的區分

眾所周知,arm體系結構分為arm狀態和thumb狀態及thumb-2狀態。在arm狀態時執行32位長度的字對齊的arm指令,thumb狀態時執行16位長度的半字對齊的thumb指令。

thumb指令集與 arm 指令的區別一般有如下幾點:

跳轉指令

程式相對轉移,特別是條件跳轉與 arm **下的跳轉相比,在範圍上有更多的限制,轉向子程式是無條件的轉移.

資料處理指令

資料處理指令是對通用暫存器進行操作,在大多數情況下,操作的結果須放入其中乙個運算元暫存器中,而不是第 3 個暫存器中.資料處理操作比 arm 狀態的更少,訪問暫存器 r8~r15 受到一定限制.除 mov 和 add 指令訪問器 r8~r15 外,其它資料處理指令總是更新 cpsr 中的 alu 狀態標誌.訪問暫存器 r8~r15 的 thumb 資料處理指令不能更新 cpsr 中的 alu 狀態標誌.

單暫存器載入和儲存指令

在 thumb 狀態下,單暫存器載入和儲存指令只能訪問暫存器 r0~r7

批量暫存器載入和儲存指令

ldm 和 stm 指令可以將任何範圍為 r0~r7 的暫存器子集載入或儲存. push 和 pop 指令使用堆疊指令 r13 作為基址實現滿遞減堆疊.除 r0~r7 外,push 指令還可以儲存鏈結暫存器 r14,並且 pop 指令可以引導程式指令pc

而程式在執行過程中,是如何區分arm狀態和thumb狀態的呢?在逆向分析過程中,經常會看到許多函式呼叫過程為形如bx sub_84c0 + 1,即函式位址為奇數。在arm執行過程中,函式呼叫的位址最後一位為1時,表示目標函式為thumb指令;否則為arm指令。然而,不管是arm和thumb狀態指令,均是偶數位元組對齊的,即函式位址最後一位肯定為0。因此,可以用最後一位判斷目標函式是否為thumb和arm狀態。

綜上,程式狀態切換可以用如下方式實現:

從arm切換到thumb:

ldr r0, =label + 1

bx r0

從thumb切換到arm:

ldr r0, = label

bx r0

上文中,label為符號的位址,因為位元組對齊緣故,最後一位肯定為0。

5.native層hook技術解析

以上 問題均是我在分析開源hook框架adbi原始碼時遇到的問題的解答,下面我將介紹一下上述幾個問題在hook中的應用。

在adbi原始檔中,hijack.c檔案中的sc陣列用於儲存在對目標函式前幾個位元組的指令修改過程中的相關指令和暫存器值。

第一條指令為ldr r0, [pc, #64],即將pc + 64位置處的記憶體的4個位元組讀到r0暫存器中。經過上面幾個章節的介紹,且此處看出指令均為4個位元組即arm指令,可知讀取的位置為當前位置 + 8 + 64 位置處的內容,即sc[18]處的內容,也即addr of libname的內容,即將函式呼叫時第乙個引數r0設定為動態庫的字串名。

第二條指令將r1暫存器,即函式的第二個引數設定為0。

第三條指令將pc的值賦值給lr暫存器,即將隨後函式呼叫後的返回位址設定為559行的那條指令,即第五條指令。

第四條指令將pc + 56位置處儲存的值賦值給pc暫存器,即當前位置 + 8 + 56位置處的值,也即sc[16]處的值,即被hook的dlopen函式的位址。

隨後函式調轉到目標函式執行,並在返回後執行559行的執行,從此開始恢復暫存器環境。也即通過這種方式完成了對目標函式的hook劫持過程。

在hook.c檔案中,實現了對目標函式的查詢及指令替換功能,詳情如下圖所示。圖中,通過對find_name函式的呼叫,得到了目標so庫中的函式funcname的位址並儲存於addr中。在**編譯過程中,編譯為thumb指令的函式,通過函式名得到的目標函式位址最後一位為1,用於表示目標函式為thumb指令集。在adbi**中,即根據該方法判斷需要hook的目標函式是thumb指令集還是arm指令集。arm指令集由於是4位元組對齊的,因此最後2位總是為0,據此判斷是否是arm指令。根據不同的指令集實現不同的指令替換,並完成hook功能。 

以上即是我在研究adbi原始碼過程中碰到的需要深入了解的一些基本概念及其具體應用,了解這些能對hook的實現原理能有較為深刻的理解,並據此編寫自己的簡易的hook方案。

6.總結

1、在arm處理器架構中,pc暫存器通常是指向當前指令後的第三條指令位址,即在arm指令是+8,thumb指令時+4。 

2、 arm/thumb狀態切換是根據目標函式位址最後一位是否為0來進行判斷,並用bx指令實現。

3、pc指向下一條正在取址的指令。

ARM彙編基礎之核心暫存器詳解

一文教你搞清楚arm cortex m3核心的暫存器 本文以arm的cortex m3核心為例,對於arm的核心暫存器做乙個簡單的介紹,並且市面上cortex m3比較容易可以買到,可以結合實踐加深對arm彙編的理解。arm的暫存器分為通用暫存器和特殊功能暫存器,從r0 r15,其中,r0 r12都...

ARM中pc lr暫存器

pc 程式計數器 windows 中 eip 解析 處理器要執行的程式 指令序列 都是以二進位制 序列方式預儲存在計算機的儲存器中,處理器將這些 逐條 地取到處理器中再解碼 執行,以完成整個程式的執行。為了保證程式能夠連續地執行下去,cpu必須具 有某些手段來確定下一條取指指令的位址.程式計數器 p...

ARM中斷暫存器詳解

s3c2440的中斷暫存器 1.中斷分兩大類 內部中斷和外部中斷。2.外部中斷。24個外部中斷占用gpf0 gpf7 eint0 eint7 gpg0 gpg15 eint8 eint23 用這些腳做中斷輸入,則必須配置引腳為中斷,並且不要上拉。具體參考datesheet資料手冊。暫存器 extin...