【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!

相关文章
相关标签/搜索