看完這篇彙編你就知道我有多會編程序員
做爲iOS開發,程序崩潰猶如屢見不鮮,秉着沒有崩潰也要製造崩潰的原則編程
我天天都吃的很飽
swift
但學藝不精的我常常有這樣的困擾,每次崩潰都定位到一堆 相似緩存
movq $0x0, 0xc7a(%rip)
的天書裏面,慌亂的我 只能狂點下一步sass
逃離這些洪水猛獸bash
是誰悄然無聲打開了 Always Show Disassembly
這扇大門?函數
但三過家門而又不入,也並不是個人性格ui
著名的學者 沃滋基
說過:克服困難最好的解決方式 ,就是不克服spa
因而 我把門關上了
命令行
垃圾桶彙編法
顧名思義 就是 我不懂彙編,但我也要和垃圾桶同樣會裝
雖然我不知道movq
是什麼意思,但我知道move
move
的 意思是 移動
至於q,管它 q不q 的,可是e 沒了
忽然想到個人亞索沒有了e
,那我還怎麼快樂的move
我很生氣,決定深刻了解一下這個東西
彙編語言:(assembly language) 是一種用於 電子計算機、微處理器、微控制器,或其餘可編程器件的低級語言 -
維基百科
簡單來講,咱們平時寫的代碼都是 高級語言
,計算機不理解高級語言,就像你吃飯不吃塑料包裝同樣
,你吃的是裏面的東西
彙編語言是二進制指令
的 文本形式,計算機會把 咱們的代碼 轉換爲 彙編語言,彙編語言 經過機器指令 還原成 二進制代碼
,也就是所謂的 0 1
,計算機就能夠執行了。
每個 CPU 的 機器指令不一樣,因此對應的彙編語言也不一樣。
爲何須要了解寄存器?
由於彙編語言 的數據存儲 與寄存器和內存 息息相關
通常來講,數據是放在內存中的,CPU 計算的時候就去內存裏拿數據,可是
CPU 的運算速度 > 內存的運算速度
就彷彿
你吃飯的速度 > 食堂大媽打菜的速度
你受不了,大媽受得了嗎?
因此CPU 自帶了一級,二級緩存,至關於大媽讓她兒子給你送飯
問題是這個中間層仍是慢且不穩定
CPU 緩存的數據地址是 不固定的
,意味着你點了份 西紅柿蓋澆飯,讓店員給你送到座位上,店員找了半個小時,發現你坐在別人店裏
...
食屎吧雷.jpeg
因此CPU 有了寄存器,來存儲頻繁使用的數據。CPU 經過寄存器 跟 內存 間接交換數據
寄存器都有本身的名稱(如 rax ,rdx等)
你說你坐在C區21號,店員還不是分分鐘把飯塞到你嘴裏,質問你:喂,你還要飯嗎?
因此CPU 會去 指定名稱的 寄存器拿數據
,這樣速度就不快了嘛
天下武功,惟快不破。
因此爲何須要寄存器,由於它的讀寫速度夠快
說到底,寄存器依舊是一個暫存區,只是一箇中間站,真正存儲數據,操做數據的仍是內存
。
如下是內存分佈圖:
這裏簡單介紹一下堆棧
堆
heap
棧
stack
先進後出,相似羽毛球筒,先放入的羽毛球,老是最後才能拿到
在Linux 下,iterm2 敲下ulimit -a
,能夠看到棧分配的默認大小爲 8192
,也就是 8M
-t: cpu time (seconds) unlimited
-f: file size (blocks) unlimited
-d: data seg size (kbytes) unlimited
-s: stack size (kbytes) 8192
複製代碼
由於是iOS開發,因此就只稍微瞭解了 AT&T
彙編 的皮毛
雖然看起來會枯燥一點,可是理解這些比較經常使用的寄存器,對彙編代碼的理解就會有質的飛躍
以前你是門外漢
如今好歹算個半個彙編人
今天的你比昨天更博學了
iOS 模擬器、MAC OS、Linux : AT&T彙編 ;
iOS 真機: ARM 彙編
複製代碼
x86-64
中,AT&T 中經常使用的 寄存器有 16種:
AT&T 經常使用寄存器介紹:
%rax
:常做爲函數返回值。 通常來講,爲了向後兼容,64位的寄存器會兼容32的寄存器,32和64能夠一塊兒使用
64位: 8個字節 ,以 r 開頭; 32位: 4個字節,以e 開頭
,看圖
在64位的寄存器 rax中,爲了兼容分配了較低的32位,也就是4個字節 給了 eax。基本上,彙編出現的eax 就是 表明rax,eax是 rax 的一部分,其餘 部分寄存器同理
%rdi、%rsi、%rdx、%rcx、%r八、%r9
: 常做爲函數參數
r8,r9 這種32位的表示法,一般在後面加d,如r8d,r9d
%rip
: 指令指針,存儲CPU 即將執行的指令地址
即將執行: 下一條執行
指令地址: 開頭的那一串 0x100...
截取2句彙編:
7 -- 0x100000a64 <+20>: movq $0x1, 0x719(%rip)
8 -- 0x100000a6f <+31>: movl %edi, -0x34(%rbp)
複製代碼
第7行中的 0x719(%rip)
中的 rip
就是指令指針,即將執行的 地址 就是 第8行 開頭的那個地址0x100000a6f
因此這裏rip 的地址就是 0x100000a6f
,有了rip 的地址
通常來講
0x719(%rip) 就是 0x719 + %rip地址
-0x719(rip) 就是 %rip - 0x719
複製代碼
棧相關
%rbp
: 棧基址指針也稱爲幀指向,指向棧底
%rsp
: 棧指針,指向棧頂
一些比較常見的我能理解的指令
中文 | AT&T | 翻譯 |
---|---|---|
當即數 | $0x1 | 當即數就是常量,前面加$ 表示 |
尋址 mov | movq $0x1, %rdi | 將 1 賦值給 寄存器 rdi,從左往右 |
內存賦值 lea | leaq %rbp,%rax | 將rbp的 內存地址值 賦給 rax |
異或 xor | xorl %eax, %eax | 將eax 清0 ,本身異或本身 |
跳轉 jmp | jmp 0x80001 | 跳轉到函數地址爲0x80001的地址 |
間接跳轉 *() | jmp *(%rax) | rax是個內存地址,*(rax) 是拿到rax地址裏的值 |
函數調用 call | callq 0x80001 | 調用 0x80001的地址的函數,通常配合retq |
那麼這個q 是幹什麼的呢 ?callq ,leaq ,movq 都有q?
這裏的q 是 表明字節大小
b:byte 字節,操做位寬 1個字節
w:word ,2個字節
l:long ,4個字節
q: 8個字節
q意味着,寄存器操做的數據類型 須要佔用 8個字節,固然這根據你的數據大小決定
複製代碼
因此上面那句代碼
movq $0x1, 0x719(%rip)
意思是,當即數 1 尋址 (0x719 + %rip),並賦值。將 1 賦值給 (0x719 + 0x100000a6f) 這個地址,當即數1 佔用了 8個字節
介紹幾個 lldb 的經常使用指令,能夠方便咱們查閱 寄存器的值
register read/格式
: 讀取寄存器的值register read/x rax // 讀取寄存器 rax 裏面的值
x:16進制
f:浮點
d:10進制
複製代碼
register write
修改寄存器的值(lldb) register read/x rax
rax = 0x0000000000000003
(lldb) register write rax 4 // 修改成4
(lldb) register read/x rax
rax = 0x0000000000000004
複製代碼
x/4xg 0x1000002
// 將 0x1000002 地址的值,以8個字節的格式,分紅4份,16進制 展現
// 這裏是展現 和 上面的操做不太同樣,g 表示8個字節
b - byte 1字節
h - half word 2字節
w - word 4字節
g - giant word 8字節
🐷:若是數據的值不夠分紅4份,剩下的字節以0 補齊
複製代碼
幀,在電影中指每一張畫面,一種平均單位
棧幀:站着的幀,畫面立體了起來,不僅僅是一個角度,裏面包含了不少信息
包含了
每一次
* 函數調用
涉及的相關信息
局部變量、函數返回地址、函數參數等
複製代碼
咱們都知道,函數的調用是會在棧上分配內存
的,分配多少取決於函數的參數和局部變量
那麼一個函數的佔用的內存大小,函數的返回地址,咱們就須要保存起來,這就用到了棧幀
由於函數運行完畢 ,在棧上須要釋放內存,以及繼續執行上一層代碼,咱們須要上一層函數的返回地址,在本次函數執行完畢後,恢復父函數的棧幀結構
想象這樣一個場景
類比一下接力賽中,4位選手
棧頂 1 -> 2 -> 3 -> 4 棧底,每一位選手都要在拿到接力棒後,纔會開跑
那麼 1號選手,就須要保存2號選手的信息,他不須要知道 3號 和 4號
下一個接棒者 長什麼樣?身上的號碼牌?站在哪裏?
1 號選手結束以後, 賽場隊伍就只剩 2 -> 3 -> 4,此時焦點就集中在2號選手
選手跑步 -> 函數調用
選手信息 -> 棧幀保存的信息
視線焦點 -> 棧指針,指向當前選手
只有咱們清楚了下一位的接棒人(在棧中對應上一層函數)
咱們才能在本次結束以後找到正確的位置,繼續執行流程
複製代碼
至於信息的保存者? 取決於寄存器的標識 Caller Save 和 Callee Save
當子函數調用的時候,也會用到父函數的寄存器,可能會存在覆蓋寄存器的值。
* Caller Save,調用者保存
父函數調用子函數以前,將寄存器的值保存一份,這樣子函數就能夠隨意覆蓋
* Callee Save,被調用者保存
父函數不保存,交由子函數 保存和恢復 寄存器的值
複製代碼
咱們簡單的創建一個 命令行 工程,打開彙編 Always Show Disassembly
用 Swift 寫出如下代碼
func test() -> Int {
var a = 3
a = a + 1
return a
}
-> test() // 斷點指向test,run
複製代碼
程序運行起來,咱們能夠看到 ,程序斷點在 test 函數調用的地方
zzz`main:
0x100000bc0 <+0>: pushq %rbp
0x100000bc1 <+1>: movq %rsp, %rbp
0x100000bc4 <+4>: subq $0x20, %rsp
0x100000bc8 <+8>: movl %edi, -0x4(%rbp)
0x100000bcb <+11>: movq %rsi, -0x10(%rbp)
-> 0x100000bcf <+15>: callq 0x100000bf0 ; zzz.test() -> Swift.Int at main.swift:189
0x100000bd4 <+20>: xorl %edi, %edi
0x100000bd6 <+22>: movq %rax, -0x18(%rbp)
0x100000bda <+26>: movl %edi, %eax
0x100000bdc <+28>: addq $0x20, %rsp
0x100000be0 <+32>: popq %rbp
0x100000be1 <+33>: retq
複製代碼
咱們控制檯 用
si 進入 test 函數內部
能夠看到 test 內部的彙編代碼,參考下面的圖,說一說個人理解
zzz`test():
-> 0x100000bf0 <+0>: pushq %rbp
0x100000bf1 <+1>: movq %rsp, %rbp
0x100000bf4 <+4>: movq $0x0, -0x8(%rbp)
0x100000bfc <+12>: movq $0x3, -0x8(%rbp)
0x100000c04 <+20>: movq $0x4, -0x8(%rbp)
0x100000c0c <+28>: movl $0x4, %eax
0x100000c11 <+33>: popq %rbp
0x100000c12 <+34>: retq
複製代碼
子函數調用時,調用者與被調用者的棧幀結構
test 函數 一進來,就執行了下面兩句代碼
-> 0x100000bf0 <+0>: pushq %rbp
0x100000bf1 <+1>: movq %rsp, %rbp
複製代碼
一開始,test 函數 就進行了 壓棧
pushq %rbp
壓棧的是父函數 main函數
的 棧幀指針 %rbp
% rbp指向的返回地址, 是main 函數 調用完 test ,應該回到哪裏的地址,也就是當前函數test 調用開始時 棧的位置
而此時 test 函數的 %rbp ,至關因而新的%rbp
而後經過
movq %rsp, %rbp
將%rsp 也 指向 %rbp,test 棧幀 的初始位置
由於%rsp 老是指向新的元素
,因此在被 一些局部變量等 填充以後,來到了棧頂
函數的調用: 棧幀被建立 -> 填充 -> 銷燬
接着
0x100000bf4 <+4>: movq $0x0, -0x8(%rbp)
0x100000bfc <+12>: movq $0x3, -0x8(%rbp)
0x100000c04 <+20>: movq $0x4, -0x8(%rbp)
複製代碼
將 當即數 0
,賦值給 %rbp - 0x8
的 8個字節 的內存空間 用於初始化
後面又將 參數3,覆蓋,以及計算+1 的值 繼續覆蓋,這裏應該是省略了 +1 的操做
接着
movl $0x4, %eax
前面說過,rax 一般做爲返回值,eax 是 rax 的32位表示,將 當即數4賦值給 eax做爲返回值
這裏用到了movl 和 eax
,是由於 int類型佔用4個字節,只須要 4個字節便可,而寄存器 是8個字節,因此寄存器的操做後綴 是q
到如今 咱們就獲得了 test函數的 返回值 4
再來
0x100000c11 <+33>: popq %rbp
0x100000c12 <+34>: retq
複製代碼
前有 push ,後就有pop,將test 中的寄存器 %rbp 從棧中彈出,恢復調用前的 rbp
,而
retq 等價於 popq %rip,前面說過rip 表明着 下一條指令
將%rip 指令指針,重新指回 test 函數調用後的 下一條 指令
,這樣程序就能夠繼續運行了
此時的 內存分佈
而 test 函數的內存空間,隨着做用域的結束,就被釋放了
到底爲止,咱們看到了 簡單的 test 函數 a + 1的 小小的過程
請勿見笑
雖然是簡單的一個加法,可是倒是咱們入門的好盆友
相信看完此篇,此時的你一定熱血沸騰,心潮澎湃
忍不住
想把門關上
...
由於個人理解也不夠深,認知有限,若是有錯誤的理解,還請指正。
謝謝~ 撒花