一、什么是栈对齐?
栈的字节对齐,实际是指栈顶指针须是某字节的整数倍。因此下边对系统栈与MSP,任务栈与PSP,栈对齐与SP对齐 这三对概念不做区分。另外下文提到编译器的时候,实际上是对编译器汇编器连接器的统称。
之前对栈的8字节对齐理解的不透,就在网上查了好多有关栈字节对齐、还有一些ARM对齐伪指令的资料信息,又做了一些实验,把这些零碎的信息拼接在一起,总觉得理解透这个问题的话得长篇大论了。结果昨天看了AAPCS手册、然后查到了没有使用PRESERVE8伪指令出现错误的实例,突然觉得长篇大论不存在了,半篇小论这问题就能理顺了。
二、AAPCS栈使用规约
在ARM上编程,但凡涉及到调用,就需要遵循一套规约AAPCS:《Procedure Call Standard for the ARM Architecture》。这套规约里面对栈使用的约定如下:
5.2.1.1
Universal stack constraints
At all times the following basic constraints must hold:
Stack-limit < SP <= stack-base. The stack pointer must lie within the extent of the stack.
SP mod 4 = 0. The stack must at all times be aligned to a word boundary.
A process may only access (for reading or writing) the closed interval of the entire stack delimited by [SP, stack-base – 1] (where SP is the value of register r13).
Note
This implies that instructions of the following form can fail to satisfy the stack discipline constraints, even when reg points within the extent of the stack.
ldmxx reg, {..., sp, ...} // reg != sp
If execution of the instruction is interrupted after sp has been loaded, the stack extent will not be restored, so restarting the instruction might violate the third constraint.
5.2.1.2
Stack constraints at a public interface
The stack must also conform to the following constraint at a public interface:
SP mod 8 = 0. The stack must be double-word aligned.
可以看到,规约规定,栈任何时候都得4字节对齐,在调用入口得8字节对齐。
在这个约定里,栈的4字节对齐确实得任何时候都遵守,而且你想不遵守都难,因为SP的最后两位是硬件上保持0的。而对于8字节对齐,这就需要码农和编译器配合着来。需要说明的一点是,8字节对齐即使不遵守,一些情况下也没问题,只要主调和被调用例程两边把堆栈使用,传参,返回等处理好就行,也就是说两边有自己的一套约定就行。但是有时候,主调这边在调用严格遵守AAPCS的函数时,没有将栈保持在8字节对齐上,那就会出问题。
三、如何编程?
在cortex m3上编程时,对于AAPCS栈使用约定的遵守,总的来说就两条:
1. 汇编文件中需要我们亲自动手来保证遵守AAPCS栈使用约定。
(特别注意每次从汇编进入C的世界时,要保证汇编部分的编码在调用c接口时栈是8字节对齐的,不要疏忽了,因为c编译器可不负责调整。c编译器说你得送给我的SP就是8字节对齐的,我才能保证接下来的C部分没有结束之前,遵守AAPCS栈使用约定)
2. 在C文件中,由编译器来处理。
四、补充:
1. 由于程序的入口点为复位中断响应函数,一般我们都写在启动代码里,通常是一个汇编文件,然后经由汇编进入到C程序的main入口处,在调用main的时刻,为遵循AAPCS,就得在此时保持8字节对齐。
2. 对于MSP,Keil MDK为我们提供了一个用来初始化C运行库环境的函数_main,这个函数会调用_user_setup_stackheap函数,该函数将MSP的低三位清零,然后在进入main之前不对其进行更改,这样在进入main的时刻,MSP保证为8字节对齐的。
3. 对于PSP,一般在上多任务OS时会用它,对于PSP我们要比MSP更为操心点,因为MSP起码还可以通过调用_main来跳进main的方式保证进入C世界的时候是遵守约定的。而PSP全靠自己来保证每次进入C世界时是8字节对齐。
4. 另外只要是汇编文件,可配合使用汇编命令armasm --diag_warning 1546,这样汇编器就会对一些SP没有8字节对齐的地方给出警告,但是我发现汇编器并不能保证检测到所有对SP造成8字节不对齐的操作,例如直接给SP载入一个立即数这种,汇编器就发现不了。我并没有对所有会影响SP的指令进行测试(原因是不熟悉。。。),不知道1546这个警告能覆盖多少指令,所以总的来讲,对汇编文件就是睁大自己的钛合金眼,争取大部分工作都放到C中去。
五. CORTEX-M3 中断控制器的栈对齐调整功能(该功能在r2p0版本以后的内核中均默认开启,STKALIGN位默认为1)
Cortex M3 NVIC CCR寄存器(控制与配置寄存器)的STKALIGN位置1,那么在发生中断时,进入中断响应函数前,内核会首先检查当前正在使用的栈指针是否8字节对齐,如果是,则正常将xPSR,PC,LR,SP,R0-R3入栈,如果不是,则先把SP-4,调整为8字节对齐,然后将xPSR第九位置1,接着把xPSR,PC,LR,SP,R0-R3入栈,再然后才进入中断响应函数。这样可以保证程序在运行过程中,如果在栈没有发生4字节对齐的地方发生中断了,进入到中断响应函数的时候也是遵守AAPCS栈使用约定的。如果中断服务程序是做任务切换的,那么前面的情况就是将任务栈调整为对齐,然后进入异常服务程序后使用系统栈,那如果系统栈本来就是不对齐的呢?通过中断来做任务切换的情况下,中断控制器并不会对系统栈进行调整,怎么办?其实这也不用担心,以μC/OS-II为例,在cortex-m3上通常使用PendSV异常来做任务切换,即将OSCtxSw以及OSIntCtxSw都设为仅完成PendSV异常触发功能,然后在PendSV异常服务程序中进行任务切换。由于上电时刻系统处于特权级模式,只要我们保证从上电开始到第一次系统调用,使用的栈都是系统栈MSP就可以了,这样即使第一次要进入任务切换时MSP不对齐,中断向量控制器也会给调整为8字节对齐状态,虽然这个第一次任务切换后除了中断再也不会使用MSP,但只要我们同时保证所有汇编部分都不会破坏8字节对齐规约,那么从此以后MSP都会是8字节对齐的。
六、关于ALIGN属性 与 PRESERVE8伪指令
在CORTEX M3芯片的启动代码中,这两个伪指令并非必不可少,可以不要这两个伪指令。但是有了这两个伪指令,可以在确保遵守AAPCS的道路上加一道保险,使得AAPCS栈使用约定的遵守在实际编程时变得稍微容易点。
当在段定义头(即AREA伪指令的相关代码)当中使用ALIGN=?时,ALIGN属性的作用为设定该代码段或数据段的首址的对齐位置,例如ALIGN=3就表示,该段首址将被安排在2^3=8字节对齐处。需要注意的是,除了AREA的ALIGN属性,还有一个同名的ALIGN指令,ALIGN指令使用在段内部的,用来调整ALIGN指令下一条命令或数据的对齐位置。
而PRESERVE8伪指令并不会对栈进行任何修改。PRESERVE8伪指令的使用有四种方法,分别如下,其中1、2的用法是等价的:
1. PRESERVE8
2. PRESERVE8 {TRUE}
3. PRESERVE8 {FALSE}
如果不写,那么由编译器来决定在编译过程中将汇编文件标识为PRES8属性还是~PRES8属性(也即加还是不加该伪指令),但经过实验,发现编译器在加不加这条伪指令上表现的并不完全可靠。。。所以最好明确的加上是 PRESERVE8 {TRUE}还是PRESERVE8 {FALSE}。那么这条伪指令起什么作用呢?
如果你想要告诉汇编器说:“在我这个汇编文件中保证栈的8字节对齐,我这个文件对栈的任何时刻的任何操作都是8字节对齐的”,那么你就把PRESERVE8伪指令用在汇编文件中,用以向汇编器通知前面你的保证内容。汇编器就知道你这个汇编文件是8字节对齐靠谱选手,将该文件标识为PRES8属性,然后如果在你这个汇编中调用了标示了需要8字节对齐属性的文件中的函数,连接的时候就不会报错。但是假如你把这个汇编文件标示为PRESERVE8 {FALSE},然后你又在这个文件中调用了标示了需要8字节对齐属性的文件中的函数,连接时就会给出错误信息。
那么什么是标示了需要8字节对齐属性的文件呢?如果你的某个汇编文件,某些操作一定要栈8字节对齐才行,那么你就需要使用REQUIRE8伪指令来通知汇编器将该文件标识为REQ8属性,然后这个文件就是所谓的“标示了需要8字节对齐属性的文件”。
在文件较多,文件之间调用由繁多的情况下,通过PRESERVE8和REQUIRE8的配合,就能够在连接期间由编译器检查出我们写代码时不小心造成的破坏8字节对齐模块对需要8字节对齐模块的调用(经过实验发现,汇编之间是给出警告,汇编调用C则是给出错误,由于C文件中并不能直接用REQUIRE8,所以我猜编译器将C文件都通通标识为REQ8属性了,所以才会出错)。
REQUIRE8的用法同PRESERVE8
原文:http://www.cnblogs.com/reload/p/3159053.html
x86_64 Linux 运行时栈的字节对齐
前言 C语言的过程调用机制(即函数之间的调用)的一个关键特性(起始大多数编程语言也是如此)都是使用了栈数据结构提供的后进先出的内存管理原则.每一个函数的栈空间被称为栈帧,一个栈帧上包含了保存的寄存器. ...
痞子衡嵌入式:链接函数到8字节对齐地址或可进一步提升i.MXRT内核执行性能
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是i.MXRT上进一步提升代码执行性能的经验. 今天跟大家聊的这个话题还是跟痞子衡最近这段时间参与的一个基于i.MXRT1170的大项目有 ...
痞子衡嵌入式:ARM Cortex-M内核那些事(6)- 系统堆栈机制
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是ARM Cortex-M堆栈机制. 今天给大家分享的这篇依旧是2016年之前痞子衡写的技术文档,花了点时间重新编排了一下格式.前面痞子衡 ...
字节对齐导致的iOS EXC_ARM_DA_ALIGN崩溃
本文原链接: http://www.cnblogs.com/zouzf/p/4455167.html 先看一下这个链接:http://www.cnblogs.com/ren54/archive/201 ...
ARM字节对齐问题详解
一.什么是字节对齐,为什么要对齐? 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这 ...
C语言深入学习系列 - 字节对齐&;内存管理
用C语言写程序时需要知道是大端模式还是小端模式. 所谓的大端模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中:所谓的小端模式,是指数据的低位保存在内存的低地址中,而数据的高 ...
pragma pack(非常有用的字节对齐用法说明)
强调一点: #pragma pack(4) typedef struct { char buf[3]; word a; }kk; #pragma pack() 对齐的原则是min(sizeof(wor ...
linux下字节对齐
一,内存地址对齐的概念 计算机内存中排列.访问数据的一种方式,包含基本数据对齐和结构体数据对齐. 32位系统中,数据总线宽度为32,每次能够读取4字节数据.地址总线为32,最大寻址空间为4 ...
c++字节对齐编译器指令#pragma
第一种 #pragma pack(push, 1) // 先把当前对齐设置压栈,再设置为1字节对齐 struct S { char a; ]; }; #pragma pack(pop) // 恢复先前 ...
随机推荐
Java工作环境笔记
环境 1. Jvm最简生存指南: http://www.importnew.com/10127.html 2. 所有路径中,不要出现中文,即使开始的时候,调试Tomcat时,路径有中文也可以,你真不知 ...
React与ES6(四)ES6如何处理React mixins
React与ES6系列: React与ES6(一)开篇介绍 React和ES6(二)ES6的类和ES7的property initializer React与ES6(三)ES6类和方法绑定 React ...
[转]Visual Studio 2010 单元测试目录
Visual Studio 2010 单元测试共分七个部分: 普通单元测试.顺序单元测试.压力测试,Generic测试.数据库测试.UI界面测试和Web性能测试. 这个系列的博客实例程序可以在下面的链 ...
[算法] 冒泡排序 Bubble Sort
冒泡排序(Bubble Sort,台湾另外一种译名为:泡沫排序)是一种简单的排序算法.它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.走访数列的工作是重复地进行直到没 ...
YZOI Easy Round 2_化简(simplify.c/cpp/pas)
Description 给定一个多项式,输出其化简后的结果. Input 一个字符串,只含有关于字母x 的多项式,不含括号与分式,没有多余的空格. Output 一个字符串,化简后的多项式,按照次数从 ...
如何从google play下载app应用,直接下载apk
如何从google play直接下载apk by fly2004jun 2013-10-05 转载请附出处 由于某些原因,大天朝局域网访问google很多服务不能用,其中就包括google ...
word-break与word-wrap
本文列举了兼容 IE 和 FF 的换行 CSS 推荐样式,详细介绍了word-wrap同word-break的区别. 兼容 IE 和 FF 的换行 CSS 推荐样式 最好的方式是 以下是引用片段: ...
【译】TensorFlow Python Docs 之 overview
Overview 综述 TensorFlow has APIs available in several languages both for constructing and executing a ...
HTML之body标签中的相关标签
一 字体标签 字体标签包含:h1~h6......
vee-validate的使用
官网地址:http://vee-validate.logaretm.com/ 这是一个插件Vue.js可以验证输入字段,显示错误,在一个简单而强大的方法.学习vee-validate,首先可以去阅读官 ...