[TOC]
之前說過學習匯編就是學習寄存器和指令,查看代碼請連接真機聋呢。
寄存器
在arm64
匯編中寄存器是64
bit的,使用X[n]
表示,低32位以w[n]
表示
在64
位架構中有31
個64
位的通用寄存器盛嘿。
可以通過register read
查看
(lldb) register read
General Purpose Registers:
x0 = 0x0000000000000001
x1 = 0x00000002805d8a20
x2 = 0x00000002837e7980
x3 = 0x00000002805d8a20
x4 = 0x0000000000000001
x5 = 0x0000000000000001
x6 = 0x0000000100d54000
x7 = 0x0000000000000000
x8 = 0x0000200000000000
x9 = 0x000161a1d63f7945 (0x00000001d63f7945) (void *)0x01d63f7cb0000001
x10 = 0x0000000000000000
x11 = 0x000000000000006d
x12 = 0x000000000000006d
x13 = 0x0000000000000000
x14 = 0x0000000000000000
x15 = 0x00000001ca634e6d "touchesBegan:withEvent:"
x16 = 0x000000019c4cd47c libobjc.A.dylib`objc_storeStrong
x17 = 0x0000000000000000
x18 = 0x0000000000000000
x19 = 0x0000000100f17810
x20 = 0x00000002837e7920
x21 = 0x00000002805d8a20
x22 = 0x00000001ca634e6d "touchesBegan:withEvent:"
x23 = 0x0000000100e11a30
x24 = 0x00000002837e7980
x25 = 0x0000000100e11a30
x26 = 0x00000002805d8a20
x27 = 0x00000001ca62d900 "countByEnumeratingWithState:objects:count:"
x28 = 0x00000002805d8a20
fp = 0x000000016f47d730
lr = 0x00000001009866dc ArmAssembly`-[ViewController touchesBegan:withEvent:] + 84 at ViewController.m:38
sp = 0x000000016f47d720
pc = 0x0000000100986720 ArmAssembly`foo1 + 16 at ViewController.m:46
cpsr = 0x80000000
(lldb)
x0~x7
:一般是函數的參數,大于8
個的會通過堆棧傳參括袒。
/*
* ArmAssembly`foo2:
0x101062760 <+0>: sub sp, sp, #0x20
0x101062764 <+4>: str w0, [sp, #0x1c]
0x101062768 <+8>: str w1, [sp, #0x18]
0x10106276c <+12>: str w2, [sp, #0x14]
0x101062770 <+16>: str w3, [sp, #0x10]
0x101062774 <+20>: str w4, [sp, #0xc]
0x101062778 <+24>: str w5, [sp, #0x8]
0x10106277c <+28>: str w6, [sp, #0x4]
-> 0x101062780 <+32>: add sp, sp, #0x20
0x101062784 <+36>: ret
*/
void foo7(int a,int b,int c ,int d ,int e,int f,int g) {
}
/*
*ArmAssembly`foo8:
0x10024275c <+0>: sub sp, sp, #0x20
0x100242760 <+4>: str w0, [sp, #0x1c]
0x100242764 <+8>: str w1, [sp, #0x18]
0x100242768 <+12>: str w2, [sp, #0x14]
0x10024276c <+16>: str w3, [sp, #0x10]
0x100242770 <+20>: str w4, [sp, #0xc]
0x100242774 <+24>: str w5, [sp, #0x8]
0x100242778 <+28>: str w6, [sp, #0x4]
0x10024277c <+32>: str w7, [sp]
-> 0x100242780 <+36>: add sp, sp, #0x20
0x100242784 <+40>: ret
*/
void foo8(int a,int b,int c ,int d ,int e,int f,int g,int h) {
}
/*
* ArmAssembly`foo9:
0x100dc2718 <+0>: sub sp, sp, #0x30
0x100dc271c <+4>: ldr w8, [sp, #0x30] 次兆; 大于8個參數的時候 已經是從內存棧中去取數據了
0x100dc2720 <+8>: str w0, [sp, #0x2c]
0x100dc2724 <+12>: str w1, [sp, #0x28]
0x100dc2728 <+16>: str w2, [sp, #0x24]
0x100dc272c <+20>: str w3, [sp, #0x20]
0x100dc2730 <+24>: str w4, [sp, #0x1c]
0x100dc2734 <+28>: str w5, [sp, #0x18]
0x100dc2738 <+32>: str w6, [sp, #0x14]
0x100dc273c <+36>: str w7, [sp, #0x10]
0x100dc2740 <+40>: str w8, [sp, #0xc]
0x100dc2744 <+44>: add sp, sp, #0x30
0x100dc2748 <+48>: ret
*/
void foo9(int a,int b,int c ,int d ,int e,int f,int g,int h,int i) {
}
可以看到當參數的個數大于8個的時候就不會從寄存器中去取參數了。
x0
:一般表示返回值
int fooReturnValue() {
return 10;
}
匯編代碼
ArmAssembly`fooReturnValue:
0x100ece7b4 <+0>: mov w0, #0xa
-> 0x100ece7b8 <+4>: ret
通過lldb
指令
(lldb) register read x0
x0 = 0x000000000000000a
(lldb)
確實是10
pc
:表示當前執(zhí)行的指令的地址
這個類似8086
匯編里面的ip
比如我們斷點該改代碼處锹锰,查看pc
寄存器的值
(lldb) register read pc
pc = 0x0000000100b6e7b8 ArmAssembly`fooReturnValue + 4 at ViewController.m:140
(lldb)
lr
:鏈接寄存器芥炭,存放著函數的返回地址
lr
也就是x30
,這個里面存放著函數的返回地址
比如有下面2個方法恃慧,方法fooFp
方法內部調用fooFp2
void fooFp() {
int a = 4;
int b = 5;
fooFp2();
}
void fooFp2() {
int a = 2;
int b = 3;
}
fooFp
的匯編代碼
ArmAssembly`fooFp:
0x100dbe76c <+0>: sub sp, sp, #0x20 ; =0x20
0x100dbe770 <+4>: stp x29, x30, [sp, #0x10]
0x100dbe774 <+8>: add x29, sp, #0x10 ; =0x10
0x100dbe778 <+12>: mov w8, #0x5
0x100dbe77c <+16>: orr w9, wzr, #0x4
0x100dbe780 <+20>: stur w9, [x29, #-0x4]
0x100dbe784 <+24>: str w8, [sp, #0x8]
0x100dbe788 <+28>: bl 0x100dbe798 ; fooFp2 at ViewController.m:154
0x100dbe78c <+32>: ldp x29, x30, [sp, #0x10]
0x100dbe790 <+36>: add sp, sp, #0x20 ; =0x20
0x100dbe794 <+40>: ret
0x100dbe788 <+28>: bl 0x100dbe798
這行就是在調用方法fooFp2
园蝠,如果調用完fooFp2
后函數應該要繼續(xù)執(zhí)行,那么肯定是要到0x100dbe788 <+28>: bl 0x100dbe798
下一行也就是地址0x100dbe78c
處痢士,我們到fooFp2
中查看下lr
寄存器的值
(lldb) register read lr
lr = 0x0000000100dbe78c ArmAssembly`fooFp + 32 at ViewController.m:170
(lldb)
當然彪薛, 本質上還是將lr
的值給了pc
寄存器了,也就是ret
指令做的事情。
棧寄存器
分為sp
棧頂寄存器和fp
棧底寄存器善延。(如果熟悉8086匯編的話類似于分別類似于sp
和bp
)
還是用上面的方法fooFp
和fooFp2
來說明這2個寄存器训唱。
不嫌啰嗦這邊還是復制下fooFp
的匯編代碼
ArmAssembly`fooFp:
0x100dbe76c <+0>: sub sp, sp, #0x20 ; 申請棧空間
0x100dbe770 <+4>: stp x29, x30, [sp, #0x10] ; 保護寄存器的值
0x100dbe774 <+8>: add x29, sp, #0x10 ; 改變fp寄存器的值挚冤,用于執(zhí)行棧底
0x100dbe778 <+12>: mov w8, #0x5
0x100dbe77c <+16>: orr w9, wzr, #0x4
0x100dbe780 <+20>: stur w9, [x29, #-0x4]
0x100dbe784 <+24>: str w8, [sp, #0x8]
0x100dbe788 <+28>: bl 0x100dbe798 ; fooFp2 at ViewController.m:154
0x100dbe78c <+32>: ldp x29, x30, [sp, #0x10] ; 恢復之前保存的fp和lr的值
0x100dbe790 <+36>: add sp, sp, #0x20 ; 恢復sp指針
0x100dbe794 <+40>: ret
整個過程如下圖
cpsr
:程序狀態(tài)寄存器
cpsr(Current Program Status Register )
spsr (Saved Program Status Register) 異常狀況下使用
xzr
:零寄存器
表示zero register
况增,一般用來默認值,里面存儲的值都是0训挡。
-
xzr
:64位的 -
wzr
:32位的
指令
尋址方式大概規(guī)則說明
ADD R0澳骤,R0,#1 // R0 = R0 + #1 表示寄存器R0的值 + 1再賦值給R0
ADD R0澜薄,R1为肮,R2 // R0 = R1+R2
ADD R0,R1肤京,[R2] // R0←R1+[R2]
LDR R0颊艳,[R1,#4] // 忘分;R0←[R1+4]
LDR R0棋枕,[R1,#4]妒峦! // R0←[R1+4]重斑、R1←R1+4
LDR R0,[R1] 肯骇,#4 // 窥浪;R0←[R1]、R1←R1+4
LDR R0笛丙,[R1漾脂,R2] // R0←[R1+R2]
- 有
[]
一般表示是取值的意思,[R2]
表示取出R2
所存的內存地址比如是0x10000
所對應的值比如是66
胚鸯。 -
LDR R0 [R1骨稿,#4]
:寄存器R1
的內容加上4
形成操作數的有效地址,從而取得操作數存入寄存器R0
中蠢琳。 -
LDR R0啊终,[R1,#4]傲须!
:將寄存器R1
的內容加上4
形成操作數的有效地址蓝牲,從而取得操作數存入寄 存器R0
中,然后泰讽,R1
的內容自增4
個字節(jié)例衍。 -
LDR R0昔期,[R1] ,#4
:以寄存器R1
的內容作為操作數的有效地址佛玄,從而取得操作數存入寄存器R0
中硼一,然后,R1
的內容自增4
個字節(jié)梦抢。 -
LDR R0般贼,[R1,R2]
:將寄存器R1
的內容加上寄存器R2
的內容形成操作數的有效地址奥吩,從而取得 操作數存入寄存器R0
中哼蛆。
計算指令
ADD
加法
ADD R0,R1霞赫,R2 // R0 = R1 + R2
ADD R0腮介,R1,#256 // R0 = R1 + 256
ADD R0端衰,R2叠洗,R3,LSL#1 // R0 = R2 + (R3 << 1)
SUB
減法
SUB R0旅东,R1灭抑,R2 // R0 = R1 - R2
SUB R0,R1玉锌,#256 //R0 = R1 - 256
SUB R0名挥,R2,R3主守,LSL#1 // R0 = R2 - (R3 << 1)
邏輯運算
AND
邏輯與、EOR
邏輯異或榄融、ORR
邏輯或参淫、LSL
邏輯左移、LSR
邏輯右移
內存指令
一般是ST
開頭的為存數據,比如說STR
愧杯、STP
涎才、STUR
LD
開頭的表示取數據,比如說LDR
力九、LDP
耍铜、lDUR
str w8, [sp, #0x8] // 表示 將w8存放到sp+0x8表示的內存中
stur w9, [x29, #-0x4] // 表示 將w9存放到x29, #-0x4表示的內存中
stp x1, x2, [sp, #-16] // 表示 從sp-0x16對應地址的開始存放 x1、x2的表示的值
ldp x29, x30, [sp, #0x10] //表示 從sp+0x10對應地址的開始取出值賦值給x29,x30
P
:可以理解為pair
跌前;
u
: 表示負數
跳轉指令
有b
棕兼、bl
一般搭配cmp
指令使用
b
:無條件跳轉,一般是什么函數內部的if
抵乓、switch
條件判斷伴挚;
bl
:帶函數返回值的跳轉靶衍,一般是調用其他的函數;
0x100432624 <+88>: cmp w10, #0x1 ; =0x1
0x100432628 <+92>: b.le 0x100432630 ;
<1>. CMP
:將寄存器 R1 的值與立即數 0x1
相減茎芋,并根據結果設置 CPSR 的標志位
標志位的可能值如下表
條件碼 | 助記符后綴 | 標志 | 含義 |
---|---|---|---|
0000 | EQ | Z 置位 | 相等 == |
0001 | NE | Z 清零 | 不相等 != |
0010 | CS | C 置位 | 無符號數大于或等于 |
0011 | CC | C 清零 | 無符號數小于 |
0100 | MI | N 置位 | 負數 |
0101 | PL | N 清零 | 正數或零 |
0110 | VS | V 置位 | 溢出 |
0111 | VC | V 清零 | 未溢出 |
1000 | HI | C 置位 Z 清零 | 無符號數大于 |
1001 | LS | C 清零 Z 置位 | 無符號數小于或等于 |
1010 | GE | N 等于 V | 帶符號數大于或等于 |
1011 | LT | N 不等于 V | 帶符號數小于 |
1100 | GT | Z 清零且(N 等于 V) | 帶符號數大于 |
1101 | LE | Z 置位或(N 不等于 V) | 帶符號數小于或等于 |
1110 | AL | 忽略 | 無條件執(zhí)行 |
<2>. b.le 0x100432630
:表示如果w10
<= 0x1
那么就執(zhí)行0x100432630
ret
指令
- 函數返回
- 將
lr(x30)
寄存器器的值賦值給pc
寄存器器颅眶。
了解了這些基本知識讀一些匯編代碼應該沒問題了,沒有提到的自己查下資料也差不多了田弥。
ARM-GNU匯編
如果你只會arm
匯編去看runtime
匯編源碼的時候會發(fā)現還是有些東西不明白涛酗,比如下面的代碼
#if SUPPORT_TAGGED_POINTERS
.data
.align 3
.globl _objc_debug_taggedpointer_classes
_objc_debug_taggedpointer_classes:
.fill 16, 8, 0
.globl _objc_debug_taggedpointer_ext_classes
_objc_debug_taggedpointer_ext_classes:
.fill 256, 8, 0
#endif
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
[...]
ARM匯編開發(fā)指用ARM提供的匯編指令,進行ARM程序的開發(fā)偷厦。
ARM匯編開發(fā)商叹,有兩種開發(fā)方式,一種是使用ARM匯編沪哺,一種是使用ARM GNU匯編沈自。兩種匯編開發(fā),使用的匯編指令是完全一樣的辜妓,區(qū)別是宏指令枯途,偽指令,偽操作不一樣籍滴。其實兩種開發(fā)方式的區(qū)別在于所使用的編譯工具不一樣酪夷。
對于ARM匯編,使用的是ARM公司開發(fā)的編譯器孽惰,而ARM GNU匯編晚岭,是使用GNU為ARM指令集開發(fā)的編譯器,也就是arm-gcc勋功。
2種方式的不同之處就是偽操作的不同坦报,蘋果遵循的是GNU
匯編規(guī)范的。點擊這個可以查看各個偽操作的意思狂鞋,比如:
.global
:全局聲明片择;
.macro
:定義一個宏;
.align
:對齊方式
.text
:指定程序在哪個段
...
關于匯編還有很多骚揍,比如書寫匯編代碼字管,內聯匯編,有了目前的基礎信不,相信學起來也是很快的嘲叔。
感謝
- https://zh.wikipedia.org/wiki/ARM%E6%9E%B6%E6%A7%8B#cite_note-v8arch-1
- https://www.arm.com/files/downloads/ARMv8_Architecture.pdf
- http://www.lujun.org.cn/?p=3943
- https://developer.apple.com/library/archive/documentation/DeveloperTools/Reference/Assembler/000-Introduction/introduction.html#//apple_ref/doc/uid/TP30000851-CH211-SW1
(完)。