簡介
在上一篇文章 iOS彙編入門教程 中介紹了彙編在iOS開發中的應用以及ARM彙編基礎知識,本文將介紹在C或Objective-C構成的工程中如何嵌入彙編程式碼。
注意
在除錯ARM彙編時,Xcode的Build物件必須為真機,如果物件為模擬器則是x86彙編。
內聯彙編
彙編與C間接通訊
在函式中可以直接插入彙編程式碼來影響函式的執行邏輯,使用的語法為編譯指令__asm__
,注意插入彙編有可能會被編譯器忽略,因此需要加入__volatile__
修飾符保證彙編程式碼有效。
下面給出一個簡單的例子,假如我們要實現一個將數值翻一倍的簡單函式。
int double_num(int num) {
return num * 2;
}
複製程式碼
下面我們採用內聯彙編的形式實現將num的值翻倍的操作。
int double_num(int num) {
__asm__ __volatile__(
"lsl x0, x0, 1\n"
"str x0, [sp, #12]\n"
);
return num;
}
複製程式碼
lsl為左移指令,x0中儲存的為入參num的值,由於該函式未發起對其他函式的呼叫,所以不必保護現場,只有一個int型別入參,需要4byte,由於ARM64下sp定址時必須按照16byte對齊,所以該函式的呼叫棧大小為16byte,所以num變數會儲存在高地址的sp+12~sp+16
區域,因此在函式返回時會從sp+12
處取出,我們通過str
指令將翻倍之後的數值儲存在對應區域即可。
彙編與C直接通訊
在上面的例子中,為了將計算後的值作為返回值,我們採用了靜態計算變數地址的方式,這裡我們換用另一種方式,將彙編的計算結果直接儲存在C變數中,以下面的函式為例,將輸入的值翻倍數次。
int double_num_times(int num, int times) {
int ret;
__asm__ __volatile__(
"lsl x0, x0, x1\n"
"mov %0, x0"
: "=r" (ret)
:
:
);
return ret;
}
複製程式碼
這裡的x0中儲存的是num,x1儲存的是times,可見從C到彙編的通訊時非常自然的;可見彙編的後三行使用了三個冒號,這是內聯彙編與C通訊的語法,其中第一行為輸出指令,第二行為輸入指令,第三行為更改的變數列表。對於彙編到C的賦值,只需要在第一行宣告"=r" (變數識別符號)
,在彙編執行完畢後會將%0暫存器的值儲存在變數識別符號內,如果有多個變數需要賦值,可以使用%1, %2以此類推,有關內聯彙編輸入輸出的基本語法可以看這篇文章。
使用純彙編實現函式
注意: 由於C++有特殊的name mangling規則,該方法僅適用於C
除了嵌入式內聯彙編外,我們還可以使用匯編檔案來直接定義函式,在Xcode中新建檔案時,選擇Other組中的彙編檔案,即可建立一個彙編檔案並將其新增到工程的編譯單元中。
我們採用純彙編來實現一下上面的double_num_times
函式,在彙編檔案中寫入如下程式碼。
; example.s
.section __TEXT,__text,regular,pure_instructions
.ios_version_min 11, 2
.p2align 2
.global _double_num_times_asm
_double_num_times_asm:
lsl x0, x0, x1
ret
複製程式碼
第一行為段的固定寫法,段的定義將在後續的教程中詳細介紹,第四行將符號引出到全域性,從第五行開始定義了符號_double_num_times_asm
的功能邏輯,這裡的下劃線是根據C語言的name mangling規則命名的,符號將被對映為C語言的全域性函式符號double_num_times_asm
,這裡由於_double_num_times_asm
沒有呼叫到其他符號,因此不需要處理x29和x30的暫存。
通過上述的彙編程式碼,我們已經完成了函式定義,只需要通過一個標頭檔案宣告一下函式即可。
// example.h
int double_num_times_asm(int num, int times);
複製程式碼
引入標頭檔案後,即可正常使用函式。
總結
在Xcode中嵌入彙編程式碼主要依賴了C語言支援通過__asm__
引入彙編程式碼的功能,而直接使用匯編實現函式邏輯則是相當於手動幫助編譯器完成了生成彙編程式碼的過程,通過嵌入彙編可以從更大程度上把握程式的執行。