ARM非对齐访问和Alignment Fault
1、指令对齐
A64指令必须word对齐。尝试在非对齐地址取值会触发PC alignment fault。
1.1、PC alignment checking
PC(Program Counter)寄存器用来存放下一条执行指令地址,对于AArch64架构,如果PC寄存器低2位不为0,则触发PC alignment fault。
类似于Instruction Aborts异常,将非对齐地址加载到PC寄存器并不会直接触发PC alignment fault,只有当CPU尝试从该地址取指令时才会触发异常。
当取指异常发生在EL0时,CPU切换到EL1。当取指异常发生时,HCR_EL2.TGE位为1且EL2使能时,CPU切换到EL2。当取指异常发生时,CPU处于其他模式,CPU运行模式不切换。
当发生PC alignment fault时,ESR(Exception Syndrome Register)的EC域置0x22,表明异常级别。
伪代码AArch64.CheckPCAlignment()执行PC alignment check,伪代码AArch64.PCAlignmentFault()则主动触发异常。
2、数据访问对齐
2.1、数据非对齐访问
如果被访问的内存地址不按照被访问的数据类型的位宽对齐,称为非对齐访问。比如int型占4个字节,则访问int型数据的内存地址需要按照4字节对齐。
2.2、硬件非对齐访问支持
MIPS架构不支持非对齐访问。
X86架构支持非对齐访问,其实现机制是将非对齐访问指令拆分成多条指令执行,结合拼接(或者拆分)指令获取数据。缺点是牺牲性能。
ARMv5架构不支持非对齐访问。
ARMv6架构开始参考X86架构实现方式支持非对齐访问,但是是部分内存访问指令支持。
ARMv7-M架构中CCR.UNALIGN_TRP位控制是否使能对齐检查(Alignment Check),ARMv7-A、ARMv7-R、ARMv8架构中SCTLR.A位控制是否使能对齐检查,默认情况下不使能对齐检查。


如果使能对齐检查,则任何指令的非对齐访问均触发非对齐异常。
对于A32/T32代码,如果不使能对齐检查,则大部分指令的非对齐访问由CPU处理,如LDR, LDRH, STR, STRH,LDRSH, LDRT, STRT,LDRSHT,LDRHT,STRHT,TBH。其他的数据访问指令的非对齐访问都会触发非对齐异常,如STRD,LDRD。
对于A64代码,如果不使能对齐检查,则所有的load和store指令的非对齐访问均由CPU处理,但是exclusive load/store, load acquire和store release指令的非对齐访问则会触发非对齐异常,包括LDAXR, LDAXRB, LDAXRH, LDAXP, STLXR, STLXRB, STLXRH, STLXP。

对于SIMD指令,一般都是强制64字节或者128字节对齐,如果发生非对齐访问,则触发非对齐访问异常。
对于任何内存访问,如果使用SP指针作为基地址,就必须quadword对齐,否则触发栈对齐异常。
2.3、软件非对齐访问支持
部分MIPS架构,通过在VxWorks内核中对非对齐访问异常进行处理,通过多次访存操作和拼接操作来实现非对齐访问,代价是牺牲性能。ARM架构内核中也有类似的处理方式,可以通过相关的配置来控制其处理方式,详细内容查看第四节。
2.4、编译器非对齐访问支持
2.4.1、GCC编译器
使能非对齐访问:-munaligned-access
禁止非对齐访问:-mno-unaligned-access
默认情况下,ARM都是aligned-access的,如果代码中使用__attribute__((packed))定义的结构体,会出现结构体成员是非对齐的,此时如果没有使能非对齐访问会导致触发abort异常。
2.4.2、编译器优化
编译器一般支持对非对齐访问代码的优化,即在编译阶段通过多次内存访问操作拆分和拼接从而规避非对齐访问。
GCC编译选项-Ox用来指定代码优化级别,-O0表示不优化,其他优化级别下会对非对齐访问代码进行优化,比如将LDRD指令的非对齐访问拆分成多条LDR指令。
3、栈对齐
3.1、SP alignment checking
当SP(Stack Pointer)寄存器的低4位不为0时,如果当前指令使用SP作为基地址,则触发栈非对齐异常。
伪代码AArch64.CheckSPAlignment()执行stack pointer check,AArch64.SPAlignmentFault()则触发栈对齐异常。
类似于Data Aborts异常,将非对齐值加载到SP寄存器并不会直接触发异常,只有当尝试从非对齐地址取数据时才会触发异常。
4、ARM Linux内核非对齐访问
4.1、/proc/cpu/alignment
Linux内核只针对arm架构实现了非对齐访问处理机制,主要是针对LDR, STR, LDRD, LDRD, STRD, LDM, STM, LDRH, STRH指令的非对齐访问进行处理。对于arm64架构,因为ARMv8架构CPU可以处理所有LDR/STR类内存访问指令的非对齐访问,因此没有实现该机制。这样就会导致如果在arm64架构上运行64位Linux内核,而用户态为32位应用程序时,如果发生非对齐访问,则会触发异常。
|
值 |
宏定义 |
说明 |
|
0 |
UM_WARM |
内核打印Alignment Trap警告,打印进程名,pid,pc,指令,地址,和异常错误码等 |
|
1 |
UM_FIXUP |
内核尝试修复用户代码非对齐访问 |
|
2 |
UM_SIGNAL |
发生非对齐访问时,内核发送SIGBUS信号量给对应进程 |
ARMv5架构,该节点默认值为0,即忽略非对齐访问。
ARMv6及以上架构,CPU本身部分支持非对齐访问,因此基本不关心该节点值,但是对于LDM, STM, LDRD和STRD等复合指令进行非对齐访问时,仍然需要软件处理,此时,可以将该节点值设置为1,即fix up模式。
上述三种值是位映射,也可以是组合值。
4.2、非对齐访问异常处理流程
4.2.1、ARM架构
Linux内核初始化时,通过fs_initcall(alignment_init)加载非对齐访问处理模块。alignment_init()函数中首先创建/proc/cpu/alignment节点,然后通过hook_fault_code(FAULT_CODE_ALIGNMENT, do_alignment, SIGBUS, BUS_ADRALN, “alignment exception”)挂do_alignment钩子函数。当发生非对齐访问异常时,进入do_alignment中处理异常,根据获取的/proc/cpu/alignment节点值,分别进入不同的处理分支。
该部分代码位于arch/arm/mm/alignment.c中。
4.2.2、ARM64架构
Linux内核在fault_info[]中注册钩子函数,当发生非对齐访问(BUS_ADRALN)时,进入do_bad()函数,给对应进程发送SIGBUS信号量。
该部分代码位于arch/arm64/mm/fault.c中。
5、什么情况下容易发生非对齐访问?
出现alignment fault问题,通常是用户编写的代码导致。估计很多程序猿在编写代码(特别是c/c++代码)时,从未考虑过这样的问题,那是因为多数可能都在X86架构下的进行代码开发,而且没有考虑过代码的移植性,如前面所说X86硬件会自动处理非对齐问题,用户感知不到,但这种情况下,由此带来的性能损耗,用户可能也关注不到了。另一方面,部分情况下,编译器也会自动做padding处理(如对结构体的自动填充对齐),这也进一步让程序猿们减少了对alignment fault的关注。
最常见的可能导致alignment fault的代码编写方式如:
1) 指针转换
将低位宽类型的指针转换为高位宽类型的指针,如:将char * 转为int *,或将void *转为结构体指针。这类操作是导致alignment fault的最主要的来源,在分析定位问题时,需要特别关注。对于出现异常却又必须这样使用的场景,对这类转换后的指针进行访问时,如果不能确认其对应的地址是对齐的,则应该使用memcpy访问(memcpy方式不存在对齐问题)。另外,建议转换后立即使用,不要将其传递到其他函数和模块,防止扩展,带来潜在的问题。
2) 使用packed属性或者编译选项
这样的操作会关闭编译器的自动填充功能,从而使结构体中各个字段紧凑排列,如果排列时未处理好对齐,则可能导致alignment fault。一些场景下(内核中也较常见)确实需要用户自行紧凑排列结构体,可节省空间(在内存资源稀缺的场景下,很有用),此时需要特别关注对齐问题,建议通过填充的方法尽量对齐,如此可能会导致空间浪费,但是会提升访问性能,典型的“以空间换时间”的思路。如果对空间有强烈要求,而可以接受性能损失,也可以不考虑对齐,不做padding,但在访问这些结构体的数据时,需要全部使用memcpy的方式。
6、测试代码
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void sigbus_handler(int sno)
{
printf("signal %d captured\n", sno);
exit();
}
int main(int argc, char *argv[])
{
char intarray[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
signal(SIGBUS, sigbus_handler);
printf("int1 = 0x%08x, int2 = 0x%08x, int3 = 0x%08x, int4 = 0x%08x\n",
*((int *)(intarray + )),
*((int *)(intarray + )),
*((int *)(intarray + )),
*((int *)(intarray + )));
return ;
}
ARM非对齐访问和Alignment Fault的更多相关文章
- 非对齐访问(unaligned accesses)
从CPU角度看内存访问对齐 结构体成员非对齐访问所带来的思考 ARM体系中存储系统非对齐的存储访问操作 什么是cache line? cache line就是处理器从RAM load/store数据到 ...
- ARM 非对齐的数据访问操作
I’m confused about unaligned memory accesses on ARM. My understanding was that they’re not allowed — ...
- ARM非对齐操作异常解决过程
在测试MF固件时,发生一个非常诡异的异常,代码如下: CLR_DBG_Commands::Monitor_EraseMemory* cmd = (CLR_DBG_Commands::Monitor_E ...
- 从硬件到语言,详解C++的内存对齐(memory alignment)
转载请保留以下声明 作者:赵宗晟 出处:https://www.cnblogs.com/zhao-zongsheng/p/9099603.html 很多写C/C++的人都知道“内存对齐”的概念以及规则 ...
- 从硬件到语言,详解C++的内存对齐(memory alignment)(一)
作者:赵宗晟 出处:https://www.cnblogs.com/zhao-zongsheng/p/9099603.html 很多写C/C++的人都知道“内存对齐”的概念以及规则,但不一定对他有很深 ...
- ARM字节对齐问题详解
一.什么是字节对齐,为什么要对齐? 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这 ...
- CM3中数据传输对齐/非对齐方式
在CM3中,非对齐的数据传输只发生在常规的数据传送指令中,如LDR.LDRH.LDRSH.其他指令则不支持,包括: 1.多个数据的加载.存储(LDM/STM). 2.堆栈操作PUSH.POP. 3.互 ...
- C++成员变量内存对齐问题,ndk下非对齐的内存访问导致BUS_ADRALN
同样的代码,在vs下运行正常,在android ndk下却崩溃: signal 7(SIGBUS),code 1 (BUS_ADRALN),fault addr 0xe6b82793 Func(sho ...
- Best Coder Round#25 1003 树的非递归访问
虽然官方解释是这题目里的树看作无向无环图,从答案来看还是在“以1作为根节点”这一前提下进行的,这棵树搭建好以后,从叶节点开始访问,一直推到根节点即可——很像动态规划的“自底向上”. 但这棵树的搭建堪忧 ...
随机推荐
- 携程的 Dubbo 之路,值得学习!
注:本篇文章整理自董艺荃在 Dubbo 社区开发者日上海站的演讲. 1.缘起 携程当初为什么要引入 Dubbo 呢?实际上从 2013 年底起,携程内主要使用的就是基于 HTTP 协议的 SOA 微服 ...
- JDK11 | 第六篇 : Epsilon 垃圾收集器
文章首发于公众号<程序员果果> 地址 : https://mp.weixin.qq.com/s/RhGXJImhp7Xm-wDrpPomkQ 一.简介 Epsilon(A No-Op Ga ...
- phpstudy添加PHP
想在phpstudy2018里面增加一个php版本,操作如下: 一.下载php-7.2.19-ts文件,解压缩,放在相应的目录下: 二.修改Apache的配置文件1.修改httpd.conf 配置,D ...
- 块设备驱动——ramblock
一. 什么是块设备. 1.1. 一种具有一定结构的随机存取设备,对这种设备的读写是按块进行的,他使用缓冲区来存放暂时的数据,待条件成熟后,从缓存一次性写入设备或者从设备一次性读到缓冲区.可以随机访问, ...
- 原生js事件委托(事件代理)方法扩展
原生js事件委托(事件代理)方法扩展: 通过Node底层原型扩展委托方法 /** * 事件委托方法 * @param eventName {string}:事件名称,如'click' * @param ...
- Django 调试models 输出的SQL语句 定位查看结果
django 调试models变得更为简单了,不用像之前的版本, 手工去调用django query, 才能打印出之前的代码是执行的什么SQL语句. 1.3开始只需在settings.py里,配置如下 ...
- 20170309工作笔记--------如何用好dialog,想变什么样就变成什么样
(1)首先自定义一个dialog的div,并且写内容 (2)运用相应的代码进行控制,弹出dialog $(".tel").click(function() { $("#d ...
- 在JSP中<%= >,<%! %>,<% %>所代表的含义
<%! %>:是jsp中的声明标签,通常声明全局变量,常量,方法等. <% %>:<% java代码 %>,其中可以包含局部变量,java语句等. <%= % ...
- UVa10426
GCD Extreme(II) Input: Standard Input Output: Standard Output Given the value of N, you will have to ...
- IDEA开发初始化设置
一.基本设置 1. 自动生成 serialVersionUID 的设置 2. 设置文件注释 3. 隐藏项目文件(夹) .git;.gitignore;.idea;.idea/.;.mvn;mvnw;m ...