【arm】arm架構64位入門基礎:架構分析、寄存器、調用規則、指令集、程序調試以及參考手冊

Date: 2018.8.21


一、參考

https://developer.arm.com/products/architecture/instruction-sets
https://developer.arm.com/docs/ddi0487/a程序員

二、ARM64位架構分析

ARM64位採用ARMv8架構,64位操做長度,對應處理器有Cortex-A5三、Cortex-A5七、Cortex-A7三、iphones的A7和A8等。架構

AARCH64是全新32位固定長度指令集,支持64位操做數的新指令,大多數指令能夠具備32位或64位參數。iphone

ARM64位架構有兩種主要的執行狀態:ide

  1. AArch64 ——64 位執行狀態,包括該狀態的異常模型、內存模型、程序員模型和指令集支持
  2. AArch32 ——32 位執行狀態,包括該狀態的異常模型、內存模型、程序員模型和指令集支持

這些執行狀態支持三個主要指令集:函數

  1. A32(或 ARM):32 位固定長度指令集,經過不一樣架構變體加強部分 32 位架構執行環境如今稱爲 AArch32。
  2. T32 (Thumb) 是以 16 位固定長度指令集的形式引入的,隨後在引入 Thumb-2 技術時加強爲 16 位和 32 位混合長度指令集。部分 32 位架構執行環境如今稱爲 AArch32。
  3. A64:提供與 ARM 和 Thumb 指令集相似功能的 32 位固定長度指令集。隨 ARMv8-A 一塊兒引入,它是一種 全新的AArch64 指令集。
    ARM ISA 不斷改進,以知足前沿應用程序開發人員日益增加的要求,同時保留了必要的向後兼容性,以保護軟件開發投資。在 ARMv8-A 中,對 A32 和 T32 進行了一些增補,以保持與 A64 指令集一致。
三、ARM64位寄存器

主要包括64位下的ARM寄存器和NEON寄存器。
ARM架構64位寄存器:
31個通用寄存器X0~X30,以及SP(x31)和PC,共33個。其中W0~W31分別是X0~X31的低32位,以下圖所示:
這裏寫圖片描述
64位下通用寄存器關係圖post

ARM64位參數調用規則遵循AAPCS64,規定堆棧爲滿遞減堆棧。
寄存器調用規則以下:測試

  • X0~X7:用於傳遞子程序參數和結果,使用時不須要保存,多餘參數採用堆棧傳遞,64位返回結果採用X0表示,128位返回結果採用X1:X0表示。
  • X8:用於保存子程序返回地址, 儘可能不要使用 。
  • X9~X15:臨時寄存器,使用時不須要保存。
  • X16~X17:子程序內部調用寄存器,使用時不須要保存,儘可能不要使用。
  • X18:平臺寄存器,它的使用與平臺相關,儘可能不要使用。
  • X19~X28:臨時寄存器,使用時必須保存。
  • X29:幀指針寄存器,用於鏈接棧幀,使用時須要保存。
  • X30:連接寄存器LR
  • X31:堆棧指針寄存器SP或零寄存器ZXR

注意:
子程序調用時必需要保存的寄存器:X19~X29和SP(X31)。
不須要保存的寄存器:X0~X7,X9~X15ui

64位下NEON寄存器:spa

  • 32個B寄存器(B0~B31),8bit
  • 32個H寄存器(H0~H31),半字 16bit
  • 32個S寄存器(S0~S31),單子 32bit
  • 32個D寄存器(D0~D31),雙字 64bit
  • 32個Q寄存器(V0~V31),四字 128bit

不一樣位數下寄存器之間的關係以下圖所示:
這裏寫圖片描述.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

四、ARM64位指令集A64以及參考手冊

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位程序調試

方法一: 直接打印數據

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入棧、出棧更多可參考:

  1. https://community.arm.com/processors/b/blog/posts/using-the-stack-in-aarch64-implementing-push-and-pop
  2. https://community.arm.com/processors/b/blog/posts/using-the-stack-in-aarch32-and-aarch64
  3. https://stackoverflow.com/questions/40271180/push-and-pop-a-full-128-bit-neon-register-to-from-the-stack-in-aarch64

問題二:程序出現 segmention fault後,採用gdb調試
可能緣由分析:
一、段錯誤通常是因爲堆棧被破環,在存取數據時引發SIGSEGV crash,一般是因爲內存讀寫越界致使。關於SIGSEGV的解釋能夠詳見SIGSEGV與SIGBUS的區別分析
二、堆棧多是正確的,可是在存取數據時訪問的地址不對(即指針所對應的地址是無效地址,沒有物理內存對應該地址),形成訪問越界引發crash,好比含有指針地址的函數聲明與函數實現不一致會引發段錯誤。(2018.9.25 調試svac2dec庫總結經驗)

六、IOS64和ARM64的參數傳遞差別和編譯差別

(1)ARM64參數入棧都要保證8字節對齊,跟數據類型無關,而IOS64的參數入棧跟數據類型有關;
(2)ARM64參數傳遞是成對傳遞的,好比(x0,x1),(x2,x3)等,而IOS64的參數傳遞並不該遵照這一準則;
(3)ARM和IOS編譯的差異:
ARM在Linux下編譯gcc早期版本函數名前須要添加下劃線,目前最新版本的gcc(4.4.7)不須要添加,這與gcc編譯版本相關;
IOS平臺下編譯都須要添加下劃線:「_」。

七、ARM64位加載和存儲數據的幾種格式
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
八、ARM64位下程序註釋

在ARM32位下,單行註釋採用@或者//,多行註釋能夠採用/**/;
在ARM64位下,單行註釋採用//,多行註釋採用/* */;
所以爲了程序註釋的統一,建議在ARM32位和ARM64位程序中註釋都採用//的格式。


THE END!

相關文章
相關標籤/搜索