arm64 linux 除零正常返回,arm64程序调用规则
前言
这篇主要介绍arm64程序调用规则,详细分析了程序调用过程中,参数是如何传递的。Android、iOS、Linux等基本遵循这些规则,但是各个操作系统平台也有小部分自己特定的规则。下一篇,我将介绍iOS平台的特定规则。
术语介绍
术语
意义
A32
在ARMv7架构中,使用32位固定长度指令的ARM指令集。
A64
AArch64可用时的指令集。
AAPCS64
AArch64程序调用标准。(PCS:Procedure Call Standard)
AArch32
ARMv8中的32位通用寄存器,兼容ARMv7-A。
AArch64
ARMv8中的64位通用寄存器
ABI(Application Binary Interface)
汇编接口规范,跟执行环境相关,比如Linux ABI,说的是Linux环境下的汇编接口规范;
ARM-based
基于ARM
Floating point
根据上下文有这三种意思:(1)遵循IEEE 754 2008的浮点运算; (2)ARMv8浮点指令集; (3)一个被ARMv8浮点指令集和ARMv8 SIMD指令集共享的寄存器组。
Q-o-I
Quality of Implementation
SIMD
Single Instruction Multiple Data 一条指令操作多个数据
T32
T32使用可变16bit和32bit
Routine, subroutine
Routine:调用者;subroutine:被调用者
Procedure
没有返回值的函数
Function
有返回值的函数
PIC, PID
Position-independent code, position-independent data.
Program state
指程序内存和寄存器的值
Caller- saved register
调用者在调用函数之前,保存寄存器(一般入栈),函数返回后恢复寄存器(一般出栈)
Callee-saved register
被调用者(函数内部),在起始地方保存寄存器,在结束时,恢复寄存器
NGRN(The Next General-purpose Register Number )
可以理解为,记录r0-r7(见下文寄存器)使用个数,参数传递前设为0,每放一个参数进入寄存器(整型寄存器),值加1。当等于8时候,说明r0-r7寄存器使用完了,再有参数,只能放入内存了。
NSRN (The Next SIMD and Floating-point Register Number)
同上,记录v0-v7使用个数
NSAA (The next stacked argument address)
记录参数放入内存,参数传递前设为SP,所以内存中参数范围应该是 sp~NSAA。详细见下文参数传递
数据类型和对齐
基本数据类型
Type ClassMachine TypeByte
sizeNatural
Alignment
(bytes)
IntegralUnsigned byte11
Signed byte11
Unsigned half-
word22
Signed half-
word22
Unsigned word44
Signed word44
Unsigned
double-word88
Signed double-
word88
Unsigned quad-
word1616
Signed quad-
word1616
Floating PointHalf precision22
Single precision44
Double
precision88
Quad precision1616
Short vector64-bit vector88
128-bit vector1616
PointerData pointer88
Code pointer88
程序调用规则
寄存器
arm64有两种寄存器:
处理整型和指针的寄存器
通用寄存器和AAPCS64用法
寄存器
别名
意义
SP
Stack Pointer:栈指针
r30
LR
Link Register:在调用函数时候,保存下一条要执行指令的地址。
r29
FP
Frame Pointer:保存函数栈的基地址。
r19...r28
Callee-saved registers(含义见上面术语解释)
r18
平台寄存器,有特定平台解释其用法。如果平台未把其做特殊用途,可当做临时寄存器使用。(iOS平台保留的寄存器,应用不可使用)
r17
IP1
The second intra-procedure-call temporary register (can be used by call veneers and PLT code); at other times may be used as a temporary register.
r16
IP0
The first intra-procedure-call scratch register (can be used by call veneers and PLT code); at other times may be used as a temporary register.
r9...r15
临时寄存器
r8
在一些情况下,返回值是通过r8返回的
r0...r7
r0-r7在函数调用过程中传递参数和返回值
NZCV
状态寄存器:N(Negative)负数 Z(Zero) 零 C(Carry) 进位 V(Overflow) 溢出
arm64有31个通用整型寄存器,r0-r30。当使用64bits时候,命名x0-x30;使用32bits时,命名w0-w30。当寄存器在此程序调用标准中具有固定角色时,使用大写。
SIMD 和 Floating-Point寄存器
ARM64有32个寄存器v0-v31,用于处理SIMD和浮点运算。长度不同称谓也不同,b,h,s,d,q,分别代表byte(8位),half(16位),single(32位),double(64位),quad(128位)。v0-v7在函数调用过程中传递参数和返回值;v8-v15 是Callee-saved registers(见术语解释),且是保存前64bits(更大的位数,调用者负责保存),v0-v7, v16-v31不需要保存或者调用者保存。
进程、内存、栈
一个进程的内存可分为5类:
代码区。只能被进程读,不可些。
可写静态数据。
只读静态数据。
堆。
栈。
可写静态数据可以细分为初始化,零初始化和未初始化数据。 除了栈之外,其它4类内存不需要占用连续的内存。 进程必须具有一些代码和栈,其它3类不是必须有。
堆是由进程管理的内存区域, 通常用于创建动态数据对象。
内存地址
地址空间包括一个或多个不相交的区域。 区域不能跨越零地址,但是可以从零开始。
标记寻址(tagged addressing)的使用是特定平台解释的。 当禁用标记寻址时,指针的所有64位都被传递到地址转换系统。 启用标记寻址时,为了进行地址转换,将忽略指针的前八位。注意:此tagged addressing,非iOS里的Tagged Pointer。
栈
栈是连续的内存空间,可用于存储局部变量和参数传递(用于传递参数的寄存器不够用时候)。栈地址是从高到低,栈的地址保存在SP中。
栈使用限制:
Stack-limit < SP <= stack-base
进程只能访问这个范围内的栈空间:[SP, stack-base – 1]
SP mod 16 = 0
函数调用
A64指令集包含函数调用指令BL和BLR。
执行BL:PC(program counter)顺序的下一个值,也就是返回地址(函数调用完成返回要执行指令的地址),存放到LR中,将跳转地址传给PC。BLR跟BL类似,只不过PC的值是从寄存器中读取。
参数传递
参数可通过r0-r7、v0-v7,栈来传递;如果参数个数不多,且参数可放进寄存器,那仅用寄存器传递参数。
可变参数
可变参数可分为命名参数(已声明的)和匿名参数(可选的参数)。
当可变参数的函数,调用时候,没有可选参数时候(只有已声明的参数),调用过程和固定参数的函数一样的。
参数传递规则
参数传递从概念上可以分为2阶段:
从源语言参数类型到机器类型的映射(不同源语言,映射规则不同)
整理机器类型,生成最终参数列表
参数传递过程分为3个阶段:
阶段A – 初始化
(在开始处理参数之前,该阶段仅执行一次)
NGRN = 0 (NGRN意义,见术语)
NSRN = 0 (NSRN意义,见术语)
NSAA = SP(NSAA意义,见术语)
阶段B - 预填充和扩展参数 (把参数列表中的每一个参数,去匹配下面规则,第一个被匹配到的规则,应用到该参数上。)
如果参数类型是复合类型,调用者和被调用者都不能确定其大小,则将参数复制到内存中,并将参数替换为指向该内存的指针。 (C / C ++语言中没有这样的类型,其它语言存在。)
如果参数是HFA或HVA类型,则参数不修改。
如果参数是大于16个字节的复合类型,调用者申请一个内存,将参数复制到内存里去,并将参数替换为指向该内存的指针。
如果参数是复合类型,则参数的大小向上舍入为最接近8个字节的倍数。(例如参数大小为9字节,修改为16字节)
阶段C- 把参数放到寄存器或栈里 (参数列表中的每个参数,将依次应用以下规则,直到参数放到寄存器或栈里,此参数处理完成,然后再从参数列表中取参数。注: 将参数分配给寄存器时,寄存器中未使用的位的值不确定。 将参数分配给栈时,未填充字节的值不确定。)
(1) 如果参数是half(16bit),single(16bit),double(32bit)或quad(64bit)浮点数或Short Vector Type,并且NSRN小于8,则将参数放入寄存器v[NSRN]的最低有效位。 NSRN增加1。 此参数处理完成。
(2) 如果参数是HFA(homogeneous floating-point aggregate)或HVA(homogeneous short vector aggregate)类型,且NSRN + (HFA或HVA成员个数) ≤ 8,则每个成员依次放入SIMD and Floating-point 寄存器,NSRN=NSRN+ HFA或HVA成员个数。此参数处理完成。
(3) 如果参数是HFA(homogeneous floating-point aggregate)或HVA(homogeneous short vector aggregate)类型,但是NSRN已经等于8(说明v0-v7被使用完毕)。则参数的大小向上舍入为最接近8个字节的倍数。(例如参数大小为9字节,修改为16字节)
(4) 如果参数是HFA(homogeneous floating-point aggregate)、HVA(homogeneous short vector aggregate)、quad(64bit)浮点数或Short Vector Type,NSAA = NSAA+max(8, 参数自然对齐大小)。
(5) 如果参数是half(16bit),single(16bit)浮点数,参数扩展到8字节(放入最低有效位,其余bits值不确定)
(6) 如果参数是HFA(homogeneous floating-point aggregate)、HVA(homogeneous short vector aggregate)、half(16bit),single(16bit),double(32bit)或quad(64bit)浮点数或Short Vector Type,参数copy到内存,NSAA=NSAA+size(参数)。此参数处理完成。
(7) 如果参数是整型或指针类型、size(参数)<=8字节,且NGRN小于8,则参数复制到x[NGRN]中的最低有效位。 NGRN增加1。 此参数处理完成。
(8) 如果参数对齐后16字节,NGRN向上取偶数。(例如:NGRN为2,那值保持不变;假如NGRN为3,则取4。 注:iOS ABI没有这个规则)
(9) 如果参数是整型,对齐后16字节,且NGRN小于7,则把参数复制到x[NGRN] 和 x[NGRN+1],x[NGRN]是低位。NGRN = NGRN + 2。 此参数处理完成。
(10) 如果参数是复合类型,且参数可以完全放进x寄存器(8-NGRN>= 参数字节大小/8)。从x[NGRN]依次放入参数(低位开始)。未填充的bits的值不确定。NGRN = NGRN + 此参数用掉的寄存器个数。此参数处理完成。
(11) NGRN设为8。
(12) NSAA = NSAA+max(8, 参数自然对齐大小)。
(13) 如果参数是复合类型,参数copy到内存,NSAA=NSAA+size(参数)。此参数处理完成。
(14) 如果参数小于8字节,参数设置为8字节大小,高位bits值不确定。
(15) 参数copy到内存,NSAA=NSAA+size(参数)。此参数处理完成。
从上面规则,可以得到经验:
处理完参数列表中所有的参数后,调用者一定知道传递参数用了多少栈空间。(NSAA - SP)
浮点数和short vector types通过v寄存器和栈传递,不会通过r寄存器传递。(除非是小复合类型的成员)
寄存器和栈中,参数未填充满的部分的值,不可确定。
函数返回结果
函数返回方式取决于返回结果的类型。
如果返回是类型T,如下
void func(T arg)
复制代码
arg值通过寄存器(组)传递,返回的结果也是通过相同的寄存器(组)返回。
2. 调用者申请内存(内存大小足够放入返回结果且是内存对齐的),将内存地址放入x8中传递给子函数,子函数运行时候,可以更新x8指向内存的内容,从而将结果返回。
结语
假如文章有不对地方,欢迎大家留言指出;或者给我发邮件(wu_k_k@foxmail.com)。
引用
--EOF-- 转载请保留链接,谢谢
arm64 linux 除零正常返回,arm64程序调用规则相关推荐
- Ubuntu 上使用 qemu 模拟 Arm64 linux
Ubuntu 上用 qemu 模拟 Arm64 linux 环境配置 1. 安装Arm64交叉编译工具链 2. 安装qemu 下载源码包 编译 编译Linux kernel 编译qemu 编译busy ...
- ARM64 Linux 内核页表的块映射
作者 | 宋宝华 责编 | 张文 头图 | CSDN 下载自视觉中国 出品 | CSDN(ID:CSDNnews) 内核文档 Documentation/arm64/memory.rst 描述了 A ...
- 基于 arm64 Linux nanosleep 系统调用流程分析
nanosleep (高分辨率睡眠)可实现纳秒级的睡眠,暂停调用线程的执行.在 Linux 内核中是如何实现的?下面基于 arm64 cpu 架构去分析. #include <time.h> ...
- ARM DS-5单步调试ARM64 linux 内核
目录 1 介绍 2 开发环境 3 准备工作 3.1 Ubuntu环境准备 3.2 源代码准备 3.3 DS-5准备 3.4 使用DS-5调试源码 3.4.1 建立源码工程 3.4.2 创建debug配 ...
- linux中memcpy实现分析,ARM64 的 memcpy 优化与实现
如何优化 memcpy 函数 Linux 内核用到了许多方式来加强性能以及稳定性,本文探讨的 memcpy 的汇编实现方式就是其中的一种,memcpy 的性能是否强大,拷贝延迟是否足够低都直接影响着整 ...
- ARM64 Linux内核起始虚拟地址
0xFFFFFE0000000000 是ARM64 Linux起始的虚拟地址,第一个虚拟地址
- QEMU启动ARM64 Linux内核
目录 前言 前置知识 virt开发板 ARM处理器家族简介 安装qemu-system-aarch64 安装交叉编译工具 交叉编译ARM64 Linux内核 交叉编译ARM64 Busybox 使用b ...
- Linux Zero-copy零拷贝技术:源码示例
<Linux Zero-copy零拷贝技术:源码示例> <Linux Zero-copy零拷贝技术全面揭秘> <什么是mmap?零拷贝?DMA?> <Linu ...
- 面试题:如何理解 Linux 的零拷贝技术?
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 本文讲解 Linux 的零拷贝技术,云计算是一门很庞大的技术学科, ...
最新文章
- 在Windows 7下面IIS7的安装和 配置ASP的正确方法
- vb mysql 字符串转日期_VB常用函数表
- CoreJava 笔记总结-第三章 Java的基本程序设计结构
- [react] react是哪个公司开发的?
- 云计算的概念 - 初识云计算知识专栏(1)
- python键盘输入转换为列表_Python键盘输入转换为列表的实例
- 软件版本的GA、RC的具体含义
- pl/sql 存储过程实例
- JS基础-事件模型(事件事件流自定义事件事件冒泡/代理)
- OPC DA 与 OPC UA区别
- 服装进销存2022年排行榜,新手小白必看!
- 一发入魂双链表(十字链表)
- udpping检测与对端udp协议通信状况
- OpenStack安装Placement组件部署(四)
- Linux命令——性能监控glance命令详解
- 你真的懂 MP4 格式吗?
- PHP 登录TPlink路由器
- 开发者还能这样开发小游戏变现
- 新电脑win10 改win7 要注意
- iPhone12概念图来袭,最为期待的样子来了
热门文章
- 2019工作榜单:程序员吸金榜,AI排第一,这个我服!
- 华为5G设备全球分布图曝光:欧洲占总量近6成;地平线发布首款车规级AI芯片,名叫征程2.0;奥迪与比亚迪达成电池供货协议……...
- shell 提取sql 的字段名表名_Mysql 常用SQL语句集锦(仅学习)
- js传中文参数 java取_js中文转码传输java后台 适用于用url传递中文参数
- linux:根据关键字或日期查找日志
- java 异常 日志_java中的异常、断言、日志(一)
- cygwin 远程连接linux,Cygwin解决Windows远程登录linux服务器
- 华为卡槽打不开怎么办_17500元!华为5G折叠手机刷屏,有人焦虑连夜开会…
- Arrays类详细讲解
- linux路由信息预览为空,route - 显示并设置Linux中静态路由表