https://developer.arm.com/products/architecture/instruction-sets
https://developer.arm.com/docs/ddi0487/a程序員
ARM64位採用ARMv8架構,64位操做長度,對應處理器有Cortex-A5三、Cortex-A5七、Cortex-A7三、iphones的A7和A8等。架構
AARCH64是全新32位固定長度指令集,支持64位操做數的新指令,大多數指令能夠具備32位或64位參數。iphone
ARM64位架構有兩種主要的執行狀態:ide
這些執行狀態支持三個主要指令集:函數
主要包括64位下的ARM寄存器和NEON寄存器。
ARM架構64位寄存器:
31個通用寄存器X0~X30,以及SP(x31)和PC,共33個。其中W0~W31分別是X0~X31的低32位,以下圖所示:
64位下通用寄存器關係圖post
ARM64位參數調用規則遵循AAPCS64,規定堆棧爲滿遞減堆棧。
寄存器調用規則以下:測試
注意:
子程序調用時必需要保存的寄存器:X19~X29和SP(X31)。
不須要保存的寄存器:X0~X7,X9~X15ui
64位下NEON寄存器:spa
不一樣位數下寄存器之間的關係以下圖所示:
.net
其中S0是D0的低半部分,D0是V0的低半部分 。
注意:
64位下NEON寄存器與32位下NEON寄存器之間的關係不一樣!
neon寄存器 v0~v31使用說明:
v0~v7:用於參數傳遞和返回值,子程序不須要保存;
v8~v15:子程序調用時必須入棧保存(低64位);
v16~v31:子程序使用時不須要保存。
具體可參考:
http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf 5.1.2 SIMD and Floating-Point Registers
ARMv8-a指令集參考手冊:
https://developer.arm.com/docs/ddi0487/a
https://static.docs.arm.com/ddi0487/a/DDI0487A_j_armv8_arm.pdf(官方標準手冊)
https://developer.arm.com/products/architecture/cpu-architecture/a-profile/docs/den0024/latest/porting-to-a64
https://community.arm.com/processors/b/blog/posts/porting-to-arm-64-bit
https://www.element14.com/community/servlet/JiveServlet/previewBody/41836-102-1-229511/ARM.Reference_Manual.pdf(指令集對比手冊)
http://profsite.um.ac.ir/~shoraka/ARMInstructionSet.pdf
http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf
http://infocenter.arm.com/help/topic/com.arm.doc.ihi0056c/IHI0056C_beta_aaelf64.pdf
http://infocenter.arm.com/help/topic/com.arm.doc.den0024a/DEN0024A_v8_architecture_PG.pdf(Programmer’s Guide)
方法一: 直接打印數據
ARM64位下打印數據的方法:
(1). 打印V寄存器:
mov w0, v0.s[0] mov w1, v0.s[1] mov w2, v0.s[2] mov w3, v0.s[3] bl _print
(2). 打印V寄存器的低64位:
mov w0, v2.s[0] mov w1, v2.s[1] bl _print
(3). 打印w寄存器
mov w0, w12 mov w1, w3 bl _print
其中print函數的定義以下:
void print(int a, int b, int c, int d) { printf("%08x %08x %08x %08x\n",a,b,c,d); }
(4).將V寄存器打印到內存的方法
.macro printf_m in1=x0, in2=x1 st1 {\in2\().2D}, [\in1\()] mov x0, \in1 bl cprintf .endm
cprintf定義以下:
void cprint(unsigned char *src8) { signed char* srcs8 = (signed char*)src8; short* srcs16 = (short*)src8; unsigned short* srcu16 = (unsigned short*)src8; int* srcs32 = (int*)src8; printf("u8:\n"); for(int i=0; i < 16; i++) { printf("%d", src8[i]); } printf("s8:\n"); for(int i=0; i < 16; i++) { printf("%d", srcs8[i]); } printf("u16:\n"); for(int i=0; i < 8; i++) { printf("%d", srcu16[i]); } printf("s16:\n"); for(int i=0; i < 8; i++) { printf("%d", srcs16[i]); } printf("s32:\n"); for(int i=0; i < 4; i++) { printf("%d", srcs32[i]); } }
方法二: GDB調試
詳細調試方法能夠參考:GDB調試方法
對於neon寄存器入棧:
.macro push_v_regs stp d8, d9, [sp, #-16]! stp d10, d11, [sp, #-16]! stp d12, d13, [sp, #-16]! stp d14, d15, [sp, #-16]! .endm .macro pop_v_regs ldp d14, d15, [sp], #16 ldp d12, d13, [sp], #16 ldp d10, d11, [sp], #16 ldp d8, d9, [sp], #16 .endm
至於要用的是v8~v15寄存器,爲何成了壓d8~d15? 具體緣由能夠參考:http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf 5.1.2小節 SIMD and Floating-Point Registers
Registers v8-v15 must be preserved by a callee across subroutine calls; the remaining registers (v0-v7, v16-v31) do not need to be preserved (or should be preserved by the caller). Additionally, only the bottom 64-bits of each value stored in v8-v15 need to be preserved; it is the responsibility of the caller to preserve larger values.
在採用gdb調試程序時遇到如下兩個問題:
問題一:在對v寄存器(v8~v15)入棧後,採用gdb調試會出現下面的問題:
/build/gdb-qLNsm9/gdb-7.11.1/gdb/aarch64-tdep.c:334: internal-error: aarch64_analyze_prologue: Assertion
inst.operands[0].type == AACH64_OPAND_Rt
failed.
解決方案:
經過分析可知,在對neon寄存器(v8~v15)進行入棧後採用gdb調試會出現報錯,沒法實如今存在對neon寄存器入棧的彙編代碼進行gdb調試。這是當前gdb版本7.11.1存在的堆棧問題,是屬於gdb自己存在的bug,能夠經過升級gdb版本實現調試。
另外能夠採用st1,ld1對SP存取數據的方式進行臨時替換,固然該方案僅用於調試,經過測試可知,採用該方式替代stp,ldp入棧出棧後子程序能夠獲得正確的結果,而且不會影響調用者中的值。對於這點尚存在疑問?
關於採用st1,ld1方式入棧出棧的說明:
單獨採用st1,ld1方式進行入棧出棧,測試可知不會影響調用者中的值。
採用st1,ld1方式進行入棧出棧,中間存在大量彙編代碼,進行測試可知:可能會影響調用者的值。打印輸出時間信息爲0,不能正常顯示調用者的值,可是子程序能夠獲得正確的值。
.macro push_v_regs_d sub sp, sp, #128 st1 {v8.8H, v9.8H}, [sp], #32 st1 {v10.8H, v11.8H}, [sp], #32 st1 {v12.8H, v13.8H}, [sp], #32 st1 {v14.8H, v15.8H}, [sp] .endm .macro pop_v_regs_d ld1 {v14.8H, v15.8H}, [sp] sub sp, sp, #32 ld1 {v12.8H, v13.8H}, [sp] sub sp, sp, #32 ld1 {v10.8H, v11.8H}, [sp] sub sp, sp, #32 ld1 {v8.8H, v9.8H}, [sp] add sp, sp, #128 .endm
關於SP入棧、出棧更多可參考:
問題二:程序出現 segmention fault後,採用gdb調試
可能緣由分析:
一、段錯誤通常是因爲堆棧被破環,在存取數據時引發SIGSEGV crash,一般是因爲內存讀寫越界致使。關於SIGSEGV的解釋能夠詳見SIGSEGV與SIGBUS的區別分析。
二、堆棧多是正確的,可是在存取數據時訪問的地址不對(即指針所對應的地址是無效地址,沒有物理內存對應該地址),形成訪問越界引發crash,好比含有指針地址的函數聲明與函數實現不一致會引發段錯誤。(2018.9.25 調試svac2dec庫總結經驗)
(1)ARM64參數入棧都要保證8字節對齊,跟數據類型無關,而IOS64的參數入棧跟數據類型有關;
(2)ARM64參數傳遞是成對傳遞的,好比(x0,x1),(x2,x3)等,而IOS64的參數傳遞並不該遵照這一準則;
(3)ARM和IOS編譯的差異:
ARM在Linux下編譯gcc早期版本函數名前須要添加下劃線,目前最新版本的gcc(4.4.7)不須要添加,這與gcc編譯版本相關;
IOS平臺下編譯都須要添加下劃線:「_」。
ld1 {v20.8H, v21.8H}, [x1] @ 從x1指向的存儲單元位置一次性加載128*2位數據到v20和v21中 ld1 {v1.8B}, [x1], x2 @ 從x1指向的存儲單元位置加載64位數據到v1的低64位中,而後x1=x1+x2 ld1 {v18.S}[0], [x0], x1 @ 將x0地址裏面的數據取32位加載到v18的最低32位,而後x0=x0+x1 ld1r {v30.8H}, [x1] @ 從x1地址中以16位爲單位取128位加載到v30中。 st1 {v30.8H}, [x1], #16 @ 將 寄存器v30中128位數據存儲到x1地址處,而後x1=x1+16 st1 {v0.S}[0], [x0], x2 @ 將 寄存器v0的低32位數據存儲到x0地址處嗎,而後x0=x0+x2
在ARM32位下,單行註釋採用@或者//,多行註釋能夠採用/**/;
在ARM64位下,單行註釋採用//,多行註釋採用/* */;
所以爲了程序註釋的統一,建議在ARM32位和ARM64位程序中註釋都採用//的格式。
THE END!