arm堆栈操作(DOC)
GNUARM汇编指令---.word
GNUARM汇编指令---.word第⼀部分 Linux下ARM汇编语法尽管在Linux下使⽤C或C++编写程序很⽅便,但汇编源程序⽤于系统最基本的初始化,如初始化堆栈指针、设置页表、操作 ARM的协处理器等。
初始化完成后就可以跳转到C代码执⾏。
需要注意的是,GNU的汇编器遵循AT&T的汇编语法,可以从GNU的站点()上下载有关规范。
⼀. Linux汇编⾏结构任何汇编⾏都是如下结构:[:] [} @ comment[:] [} @ 注释Linux ARM 汇编中,任何以冒号结尾的标识符都被认为是⼀个标号,⽽不⼀定⾮要在⼀⾏的开始。
【例1】定义⼀个"add"的函数,返回两个参数的和。
.section .text, “x”.global add @ give the symbol add external linkageadd:ADD r0, r0, r1 @ add input argumentsMOV pc, lr @ return from subroutine@ end of program⼆. Linux 汇编程序中的标号标号只能由a~z,A~Z,0~9,“.”,_等字符组成。
当标号为0~9的数字时为局部标号,局部标号可以重复出现,使⽤⽅法如下:标号f: 在引⽤的地⽅向前的标号标号b: 在引⽤的地⽅向后的标号【例2】使⽤局部符号的例⼦,⼀段循环程序1:subs r0,r0,#1 @每次循环使r0=r0-1bne 1f @跳转到1标号去执⾏局部标号代表它所在的地址,因此也可以当作变量或者函数来使⽤。
三. Linux汇编程序中的分段(1).section伪操作⽤户可以通过.section伪操作来⾃定义⼀个段,格式如下:.section section_name [, "flags"[, %type[,flag_specific_arguments]]]每⼀个段以段名为开始, 以下⼀个段名或者⽂件结尾为结束。
arm函数调用中的堆栈变化
arm函数调用中的堆栈变化在计算机的运行过程中,函数调用是一种常见的操作。
当程序调用一个函数时,需要先将当前的运行状态(例如当前指令的地址、堆栈指针等)保存在堆栈中,然后跳转到函数中执行。
函数执行完毕后,再从堆栈中恢复之前的运行状态,继续执行原来的程序。
这个过程中,堆栈扮演了一个非常重要的角色。
本文将介绍在ARM架构中,函数调用时堆栈的变化。
1. 堆栈的基本概念在程序中,有一片内存区域被用来存放函数的局部变量和一些临时变量,称为堆栈。
堆栈是一个先进后出的数据结构,即最后存入的数据最先弹出。
当程序调用一个函数时,会在堆栈中分配一段空间来存放函数的参数、局部变量、返回地址等信息。
当函数返回时,这些信息会从堆栈中弹出,恢复程序之前的状态。
在ARM架构中,堆栈的地址是4字节对齐的,即堆栈指针(SP)的值必须是4的倍数。
这是因为ARM指令集中的大多数指令都是以4字节为单位的,如果SP不是4字节对齐的,那么执行指令时会出错。
2. 函数调用时堆栈的变化当程序调用一个函数时,堆栈的变化可以分为以下几个步骤:(1)保存寄存器在ARM架构中,函数调用过程中一些重要的状态信息通常保存在寄存器中。
为了不影响原程序的运行,需要在堆栈中保存这些寄存器的值。
这些寄存器包括:R0~R3(函数参数)、R14(LR,返回地址)、R13(SP,堆栈指针)、R11(FP,帧指针)等。
在进入函数之前,需要将这些寄存器的值压入堆栈中。
具体操作为:``` PUSH {R0-R3, R11, LR} ```以上指令将R0~R3、R11、LR的值压入堆栈中。
其中,LR保存的是返回地址,R11保存的是帧指针(Frame Pointer,FP)。
FP是一个指针,指向当前函数的栈帧,用于访问局部变量。
堆栈指针SP也需要被保存,但不是在这里保存,而是在进入函数之前保存。
(2)分配空间在堆栈中为当前函数分配空间,用于存放参数、局部变量和其它临时变量。
分配的空间的大小由当前函数所需的局部变量和参数决定。
51单片机堆栈操作指令的用法
51单片机堆栈操作指令的用法51单片机是一种非常常用的单片机芯片,其指令集非常丰富,其中包含了很多堆栈操作指令。
堆栈操作指令是用来进行数据的入栈和出栈操作的指令,通过堆栈操作指令,我们可以方便地保存和恢复程序执行中的临时数据,提高代码的灵活性和效率。
本文将详细介绍51单片机堆栈操作指令的用法,帮助读者更好地理解和运用这些指令。
一、堆栈简介堆栈(Stack)是一种特殊的数据结构,具有后进先出(LIFO)的特点。
在51单片机的内部RAM中,有一段专门用来存放堆栈的空间,这段空间的大小为128字节(地址为0x07Fh至0x080h)。
在程序执行过程中,我们可以通过堆栈操作指令将数据入栈或者出栈,进栈是将数据放入堆栈,出栈是将数据从堆栈中取出。
二、堆栈操作指令51单片机的指令集中包含了以下几条堆栈操作指令:1. PUSH 指令PUSH指令用于将8位数据入栈,将要入栈的数据放入寄存器A中,通过PUSH 指令可以将A的数据压入堆栈。
PUSH指令的实际操作是将A的数据先放入栈顶指针(SP)所指向的内存单元中,然后将SP的值减1,即栈顶指针向下移动一个位置。
2. POP 指令POP指令用于将数据出栈,即从堆栈中取出一个8位数据,并放入寄存器A中。
POP指令的实际操作是将栈顶指针向上移动一个位置,然后将栈顶指针所指向的内存单元中的数据取出,并放入A中。
3. XCH指令XCH指令用于交换A寄存器的数据和栈顶指针所指向的内存单元的数据。
具体操作是将栈顶指针所指向的内存单元中的数据取出,并放入A中,然后将A中的数据放回栈顶指针所指向的内存单元中。
4. LCALL指令LCALL指令是一个特殊的调用指令,用于将下一条指令的地址入栈,并转移到指定地址处执行。
具体操作是将下一条指令的地址(即当前指令的地址加3)入栈,然后将指定地址的值赋给程序计数器(PC)。
5. RET指令RET指令用于从子程序返回,从堆栈中取出地址,并赋给程序计数器(PC),从而实现返回到调用该子程序的地方继续执行。
ARM 的堆栈初始化详解
1、寄存器R13 在ARM 指令中常用作堆栈指针2、对于R13 寄存器来说,它对应6个不同的物理寄存器,其中的一个是用户模式与系统模式共用,另外5个物理寄存器对应于其他5种不同的运行模式。
采用以下的记号来区分不同的物理寄存器:R13_<mode>其中,mode为以下几种模式之一:usr、fiq、irq、svc、abt、und。
3、寄存器R13在ARM指令中常用作堆栈指针,但这只是一种习惯用法,用户也可使用其他的寄存器作为堆栈指针。
而在Thumb指令集中,某些指令强制性的要求使用R13作为堆栈指针。
由于处理器的每种运行模式均有自己独立的物理寄存器R13,在用户应用程序的初始化部分,一般都要初始化每种模式下的R13,使其指向该运行模式的栈空间,这样,当程序的运行进入异常模式时,可以将需要保护的寄存器放入R13所指向的堆栈,而当程序从异常模式返回时,则从对应的堆栈中恢复,采用这种方式可以保证异常发生后程序的正常执行。
4、有四种类型的堆栈:堆栈是一种数据结构,按先进后出(First In Last Out,FILO)的方式工作,使用一个称作堆栈指针的专用寄存器指示当前的操作位置,堆栈指针总是指向栈顶。
当堆栈指针指向最后压入堆栈的数据时,称为满堆栈(Full Stack),而当堆栈指针指向下一个将要放入数据的空位置时,称为空堆栈(Empty Stack)。
同时,根据堆栈的生成方式,又可以分为递增堆栈(Ascending Stack)和递减堆栈(DecendingStack),当堆栈由低地址向高地址生成时,称为递增堆栈,当堆栈由高地址向低地址生成时,称为递减堆栈。
这样就有四种类型的堆栈工作方式,ARM 微处理器支持这四种类型的堆栈工作方式,即:◎Full descending 满递减堆栈堆栈首部是高地址,堆栈向低地址增长。
栈指针总是指向堆栈最后一个元素(最后一个元素是最后压入的数据)。
ARM-Thumb过程调用标准和ARM、Thumb C/C++ 编译器总是使用Full descending 类型堆栈。
arm汇编栈指令
arm汇编栈指令Arm汇编栈指令是针对ARM处理器的汇编指令集中的一类指令。
ARM处理器是当今常见的一种芯片,在移动设备或者网络设备中使用广泛。
在ARM汇编语言中,栈指令被广泛应用,用来进行程序栈的操作。
本文将详细介绍ARM汇编栈指令,并分步骤介绍这类指令的使用。
一、什么是栈在程序设计中,栈是一种非常重要的数据结构。
它是一种特殊的数据结构,使用“后进先出”的原则来为操作提供支持。
在栈中,最后加入的数据项是第一个被取出的,而最先加入的数据项则是最后一个被取出的,这就是“后进先出”的原则。
同时,栈还支持两种基本操作PUSH和POP。
PUSH操作将数据项压入栈中,POP操作则是从栈中弹出最近压入的数据项。
二、栈指令的分类在ARM汇编语言中,栈指令通常分为两类: PUSH指令和POP指令。
PUSH指令用来把操作数压入栈中,而POP指令则是从栈中弹出操作数。
三、指令详解ARM处理器中的PUSH指令有以下几个指令:1. PUSH {registers}该指令会从指定的一组寄存器中把值压入到栈中。
例如,PUSH{r0-r3},就是把寄存器r0-r3中的值按递减的顺序压入到栈中。
2. PUSH {register_list}!该指令使用register_list中的寄存器将一个或多个寄存器的值压入到栈中。
在此指令中,感叹号!表示将程序栈指针(SP)向下移动。
而POP指令也有以下两个指令:1. POP {registers}该指令出栈指定的一组寄存器中的值,并将其写入寄存器。
例如,POP {r0-r3},就是从栈中递增取出值并将其写入寄存器r0-r3。
2. POP {register_list}!该指令从堆栈中出栈 register_list 中指定的寄存器,并将它们的值存储到 register_list 中指定的寄存器中。
四、使用方法在使用栈指令时,我们需要注意一些细节,下面我们以使用PUSH指令为例进行详细讲解。
堆栈和队列的基本操作
堆栈和队列的基本操作一、堆栈(Stack)堆栈是一种具有特殊插入和删除规则的线性数据结构。
它按照“后进先出”(Last-In-First-Out, LIFO)原则管理数据。
1.堆栈的初始化堆栈的初始化即创建一个空堆栈。
2. 入栈(Push)入栈是将数据插入到堆栈顶部的操作。
数据插入后,堆栈的长度加1、插入的数据成为新的堆栈顶部。
3. 出栈(Pop)出栈是将堆栈顶部的数据删除的操作。
删除后,堆栈的长度减1、删除的数据为原堆栈的顶部。
4. 取栈顶元素(Top)取栈顶元素是获取当前堆栈顶部的数据,而不进行删除操作。
5. 判断堆栈是否为空(IsEmpty)判断堆栈是否为空,即判断堆栈的长度是否为0。
6. 获取堆栈长度(GetSize)获取堆栈的长度,即当前堆栈中元素的数量。
堆栈可以使用数组或链表来实现。
数组实现的堆栈称为顺序堆栈,链表实现的堆栈称为链式堆栈。
堆栈的应用:-递归函数的调用和返回-表达式求值-括号匹配-浏览器前进后退功能二、队列(Queue)队列也是一种具有特定插入和删除规则的线性数据结构。
它按照“先进先出”(First-In-First-Out, FIFO)原则管理数据。
1.队列的初始化队列的初始化即创建一个空队列。
2. 入队(Enqueue)入队是将数据插入到队列尾部的操作。
数据插入后,队列的长度加1、插入的数据成为新的队列尾部。
3. 出队(Dequeue)出队是将队列头部的数据删除的操作。
删除后,队列的长度减1、删除的数据为原队列的头部。
4. 获取队首元素(Peek)获取队列头部的数据,而不进行删除操作。
5. 判断队列是否为空(IsEmpty)判断队列是否为空,即判断队列的长度是否为0。
6. 获取队列长度(GetSize)获取队列的长度,即当前队列中元素的数量。
队列也可以使用数组或链表来实现。
数组实现的队列称为顺序队列,链表实现的队列称为链式队列。
还有一种特殊的队列称为优先队列,它根据元素的优先级进行排序。
ARM启动代码中堆栈初始化的解析
ARM启动代码中堆栈初始化的解析在启动代码中有:;定义堆栈的大小SVC_STACK_LEGTH EQU 0FIQ_STACK_LEGTH EQU 0IRQ_STACK_LEGTH EQU 128ABT_STACK_LEGTH EQU 0UND_STACK_LEGTH EQU 0ResetLDR PC, ResetAddrLDR PC, UndefinedAddrLDR PC, SWI_AddrLDR PC, PrefetchAddrLDR PC, DataAbortAddrDCD 0xb9205f80LDR PC, [PC, #-0xff0]LDR PC, FIQ_AddrResetAddr DCD ResetInit UndefinedAddr DCD UndefinedSWI_Addr DCD SoftwareInterrupt PrefetchAddr DCD PrefetchAbort DataAbortAddr DCD DataAbortNouse DCD 0IRQ_Addr DCD 0FIQ_Addr DCD FIQ_Handler;未定义指令UndefinedB Undefined;软中断SoftwareInterruptB SoftwareInterrupt;取指令中止PrefetchAbortB PrefetchAbort;取数据中止DataAbortB DataAbort;快速中断FIQ_HandlerB FIQ_HandlerStackSvc DCD SvcStackSpace + (SVC_STACK_LEGTH - 1)* 4 StackIrq DCD IrqStackSpace + (IRQ_STACK_LEGTH - 1)* 4 StackFiq DCD FiqStackSpace + (FIQ_STACK_LEGTH - 1)* 4 StackAbt DCD AbtStackSpace + (ABT_STACK_LEGTH - 1)* 4 StackUnd DCD UndtStackSpace + (UND_STACK_LEGTH - 1)* 4;/* 分配堆栈空间 */AREA MyStacks, DATA, NOINIT, ALIGN=2SvcStackSpace SPACE SVC_STACK_LEGTH * 4 ;管理模式堆栈空间IrqStackSpace SPACE IRQ_STACK_LEGTH * 4 ;中断模式堆栈空间FiqStackSpace SPACE FIQ_STACK_LEGTH * 4 ;快速中断模式堆栈空间AbtStackSpace SPACE ABT_STACK_LEGTH * 4 ;中止义模式堆栈空间UndtStackSpace SPACE UND_STACK_LEGTH * 4 ;未定义模式堆栈其中,IRQ_STACK_LEGTH 为128,根据IrqStackSpace SPACE IRQ_STACK_LEGTH * 4 ;中断模式堆栈空间可知,IrqStackSpace 为128*4个字节空间的首地址,然后在StackIrq DCD IrqStackSpace + (IRQ_STACK_LEGTH - 1)* 4 中, IrqStackSpace + (IRQ_STACK_LEGTH - 1)* 4 = 128*4 + (128-1)*4 = (128+127)*4,就是说实际的irq堆栈大小为 (128+127)*4个字节,是这样的吗?回答1:是这样吗?StackIrq DCD IrqStackSpace +(IRQ_STACK_LEGTH - 1)* 4中的IrqStackSpace 为128*4个连续字节的首地址,IrqStackSpace +(IRQ_STACK_LEGTH - 1)* 4 表示一个基址加上一个内存大小的偏移量从而构成了128*4 个连续的字节空间?回答2:可如果是这样的话直接写为StackIrq DCD (IRQ_STACK_LEGTH )* 4不就可以了吗?为什么还要用到space语句呢?回答3:不是你讲的那样StackIrq DCD IrqStackSpace + (IRQ_STACK_LEGTH - 1)* 4这里的StackIrq是顶格写的,是一个标号,实质就是一个地址。
ARM堆栈分析
ARM堆栈分析ARM是一种处理器架构,常用于嵌入式系统和移动设备。
在ARM架构中,堆栈是一种用于管理函数调用和局部变量的重要数据结构。
本文将对ARM堆栈进行详细分析。
首先,我们来了解ARM堆栈的基本概念。
堆栈是一种后进先出(LIFO)的数据结构,用于存储函数调用时需要保存的信息。
在ARM架构中,堆栈是由系统自动管理的,不需要程序员手动操作。
在ARM中,堆栈使用堆栈指针(Stack Pointer,SP)来标识当前堆栈的位置。
当函数调用时,SP指针会被更新,将当前的函数调用信息(如返回地址和局部变量)压入堆栈。
当函数返回时,SP指针会被恢复,将之前保存的信息弹出堆栈。
堆栈帧是函数调用时在堆栈上分配的一块连续内存空间,用于存储函数的参数、局部变量和其他状态信息。
堆栈帧由以下几部分组成:1. 返回地址(Return Address):函数执行完毕后,需要返回到调用者继续执行。
因此,返回地址是堆栈帧中的一个重要字段。
2.参数和返回值:函数调用时,其参数会被压入堆栈。
返回值则会通过寄存器或堆栈传递给调用者。
3. 本地变量(Local Variables):函数执行过程中所需的变量空间会在堆栈上分配。
函数退出后,这些变量所占用的堆栈空间将被释放。
4. 寄存器保存(Register Saving):如果函数需要在执行过程中使用寄存器来保存临时变量等信息,那么在函数调用时,这些寄存器中的值需要保存在堆栈中。
在ARM架构中,堆栈的使用也非常重要。
ARM处理器包含一组寄存器,如程序计数器(Program Counter,PC)、堆栈指针(Stack Pointer,SP)和链接寄存器(Link Register,LR)。
在函数调用过程中,这些寄存器的值会被保存在堆栈中,以便函数返回时能够正确恢复。
在ARM中,堆栈的使用涉及到两个重要指令:PUSH和POP。
PUSH指令用于将寄存器值压入堆栈,而POP指令用于将堆栈中的值弹出到寄存器。
C语言及ARM中堆栈指针SP设置的理解与总结
C语言及ARM中堆栈指针SP设置的理解与总结1什么是栈百度这么说:栈是一种特殊的线性表,是一种只允许在表的一端进行插入或删除操作的线性表。
表中允许进行插入、删除操作的一端称为栈顶。
表的另一端称为栈底。
栈顶的当前位置是动态的,对栈顶当前位置的标记称为栈顶指针。
当栈中没有数据元素时,称之为空栈。
栈的插入操作通常称为进栈或入栈,栈的删除操作通常称为退栈或出栈。
简易理解:客栈,即临时寄存的地方,计算机中的堆栈主要用来保存临时数据,局部变量和中断/调用子程序程序的返回地址。
程序中栈主要是用来存储函数中的局部变量以及保存寄存器参数的,如果你用了操作系统,栈中还可能存储当前进线程的上下文。
设置栈大小的一个原则是,保证栈不会下溢出到数据空间或程序空间.CPU在运行程序时,会自动的使用堆栈,所以堆栈指针SP就必须要在调用C程序前设定。
CPU的内存RAM空间存放规律一般是分段的,从地址向高地址,依次为:程序段(.text)、BSS段,上面还可能会有堆空间,然后最上面才是堆栈段。
这样安排堆栈,是因为堆栈的特点决定的,堆栈的指针SP初始化一般在堆栈段的高地址,也就是内存的高地址,然后让堆栈指针向下增长(其实就是递减)。
这样做的好处就是堆栈空间远离了其他段,不会跟其他段重叠,造成修改其他段数据,而引起不可预料的后果,还有设置堆栈大小的原则,要保证栈不会下溢出到数据空间或者程序空间。
所谓堆栈溢出,是指堆栈指针SP向下增长到其他段空间,如果栈指针向下增长到其他段空间,称为堆栈溢出。
堆栈溢出会修改其他空间的值,严重情况下可造成死机. 2堆栈指针的设置开始将堆栈指针设置在内部RAM,是因为不是每个板上都有外部RAM,而且外部RAM 的大小也不相同,而且如果是SDRAM,还需要初始化,在内部RAM开始运行的一般是一个小的引导程序,基本上不怎么使用堆栈,因此将堆栈设置在内部RAM,但这也就要去改引导程序不能随意使用大量局部变量。
堆栈操作方法讲解
堆栈操作方法讲解堆栈(stack)是一种经典的数据结构,它的特点是后进先出(LIFO,Last In First Out)。
堆栈可以看作是一种特殊的线性表,它只能在表的一端进行插入和删除操作。
具体来说,堆栈的插入操作通常称为“入栈”,删除操作称为“出栈”。
堆栈常用于需要临时存储数据的场景,例如函数调用、表达式求值、括号匹配等。
在计算机科学领域,堆栈也是一种非常基础的数据结构,几乎在各个领域都有着广泛的应用。
堆栈的实现可以通过数组或链表来完成。
下面我们将详细介绍堆栈的基本操作方法,包括创建堆栈、入栈、出栈、获取栈顶元素等。
1. 创建堆栈在实际应用中,我们可以使用数组或链表来实现堆栈。
下面以数组实现为例来介绍如何创建堆栈。
首先,我们需要定义一个固定大小的数组,用来存储堆栈中的元素。
通常,我们还需要定义一个指针变量top,用来指示当前堆栈顶部元素的位置。
初始时,top 的值为-1,表示堆栈为空。
2. 入栈操作当需要向堆栈中插入一个元素时,我们先将top 的值加一,然后将元素放入数组中的对应位置即可。
以下是入栈的基本操作流程:判断堆栈是否已满(如果是数组实现的堆栈);如果堆栈未满,则将要插入的元素放入top+1 的位置;更新top 的值,指向新的堆栈顶部元素;3. 出栈操作当需要从堆栈中删除一个元素时,我们首先获取top 处的元素,然后将top 的值减一即可。
以下是出栈的基本操作流程:判断堆栈是否为空;如果堆栈非空,则将top 处的元素取出;更新top 的值,指向新的堆栈顶部元素;4. 获取栈顶元素除了入栈和出栈操作,获取栈顶元素也是堆栈的常用操作。
我们可以通过top 指针来获取堆栈顶部的元素,而不对堆栈做任何改动。
5. 示例下面我们通过一个简单的示例来展示堆栈的基本操作。
假设我们有一个大小为5 的数组来实现堆栈,初始时堆栈为空。
首先,我们将top 的值初始化为-1。
然后我们按照如下流程进行操作:入栈操作:依次将元素A、B、C 入栈;出栈操作:依次将元素C、B 出栈;获取栈顶元素:此时栈顶元素为A;通过上述示例,我们可以清晰地了解堆栈的基本操作方法。
什么是ARM中的SP(堆栈)和LR?
什么是ARM中的SP(堆栈)和LR?LR是⽤于保存函数调⽤的返回地址的。
SP是堆栈指针。
堆栈通常⽤于在函数调⽤中保存”automatic”变量和上下⽂/参数。
从概念上讲,您可以将”stack”视为您”pile”您的数据的地⽅。
您将”stacking”保留在⼀个数据之上,堆栈指针告诉您”high”的数据是”stack”。
您可以从”stack”的”top”中删除数据并缩短。
从ARM架构参考:SP, the Stack PointerRegister R13 is used as a pointer to the active stack.In Thumb code, most instructions cannot access SP. The only instructions that can access SP are those designed to use SP asa stack pointer. The use of SP for any purpose other than as a stack pointer is deprecated. Note Using SP for any purpose otherthan as a stack pointer is likely to break the requirements of operating systems, debuggers, and other software systems, causing them to malfunction.LR, the Link RegisterRegister R14 is used to store the return address from a subroutine. At other times, LR can be used for other purposes.When a BL or BLX instruction performs a subroutine call, LR is set to the subroutine return address. To perform a subroutinereturn, copy LR back to the program counter. This is typically done in one of two ways, after entering the subroutine with a BL or BLX instruction:• Return with a BX LR instruction.• On subroutine entry, store LR to the stack with an instruction of the form: PUSH {,LR} and use a matching instruction to return: POP {,PC} …次佳解决⽅案SP是堆栈寄存器,⽤于键⼊r13的快捷⽅式。
ARMC函数调用堆栈入栈顺序
ARMC函数调⽤堆栈⼊栈顺序ARM C函数调⽤堆栈⼊栈顺序堆栈指针是在函数⼀开头就确认了的,⽐如如下的xxx_func.cfi函数,它在函数的开头就将sp⾃减了0x170,这个0x170是xxx_fun.cfi函数局部变量total size + 需要⼊栈的reg total size然后会设置x29(fp,栈底指针),这⾥看到是sp - 0x110,可以看到需要⼊栈的reg total size为0x60,所以fp指向了函数局部变量列表的头部,它是不包含函数⾥的局部变量的00000000000441c8 <xxx_func.cfi>:441c8: d105c3ff sub sp, sp, #0x170441cc: a9117bfd stp x29, x30, [sp,#272]441d0: a9126ffc stp x28, x27, [sp,#288]441d4: a91367fa stp x26, x25, [sp,#304]441d8: a9145ff8 stp x24, x23, [sp,#320]441dc: a91557f6 stp x22, x21, [sp,#336]441e0: a9164ff4 stp x20, x19, [sp,#352]441e4: 910443fd add x29, sp, #0x110441e8: 90000008 adrp x8, 0 <__stack_chk_guard>当函数返回时,从stack弹出值到之前保护reg⽽⼊栈的reg⾥,这⾥可以看到出栈时顺序恰好和⼊栈时相反,即是后⼊先出,将fp、lr从stack 弹出到fp、lr即实现返回:447f4: a9564ff4 ldp x20, x19, [sp,#352]447f8: a95557f6 ldp x22, x21, [sp,#336]447fc: a9545ff8 ldp x24, x23, [sp,#320]44800: a95367fa ldp x26, x25, [sp,#304]44804: a9526ffc ldp x28, x27, [sp,#288]44808: a9517bfd ldp x29, x30, [sp,#272]4480c: 9105c3ff add sp, sp, #0x17044810: d65f03c0 ret函数调⽤stack变化,⼊栈顺序以main()⾥有int a、int b两个局部变量并call了⼀个test_func(int i, int j),在test_func()⾥有define⼀个int c变量为例,来看下调⽤test_func()时的堆栈变化:调⽤test_func时,test_func参数i、j先反序⼊栈,然后是给test_func的返回值预留空间,然后是test_func的返回地址;接下来是在test_func函数⾥将需要保存的reg⼊栈,其中x29、x30是必须要⼊栈的,此时会将fp指向当前的堆栈顶部,这⾥x29、x30是最后才⼊栈的;然后再是为test_func中的局部变量开辟堆栈空间,此时将sp指向此时的堆栈顶部:|--------------------||..... ||--------------------||int a ||--------------------||int b ||--------------------||int j ||--------------------||int i ||--------------------||test_func返回值预留 ||--------------------||test_func返回地址 ||--------------------|fp-->|x29, x30 | /*如果test_func还需要保护其它reg,将其它reg也⼊栈*/|--------------------|sp-->|int c ||--------------------|int test_func(int i, int j){int c = i + j;return c;}int main(){int a = 3;int b =4;test_func(3, 4);return0;}所以fp指针是指向的当前函数的stack底部、sp指向的是stack顶部,在fp和sp之间即是test_func的所有局部变量区间,如果test_func没有⼀个局部变量,fp、sp将指向同⼀个位置,即都指向当前函数堆栈顶部。
实验八_ARM处理器工作模式与堆栈指针实验
实验八 ARM处理器工作模式与堆栈指针设置实验1 实验目的(1) 通过实验掌握学会使用MSR和MRS指令实现ARM工作模式的切换,观察不同模式下的寄存器,尤其是状态寄存器,加深对CPU的理解;(2) 掌握ARM堆栈指针SP的设置方法和意义,在设置好的堆栈中压入适当的数据,并使用寄存器窗口、存储器窗口观察。
2 实验内容(1) 主要内容①ARM9处理器支持7中工作运行模式(如表1)特权模式:又称非用户模式。
是指除用户模式以外的6种模式。
在这些模式下程序可以访问所有的系统资源,也可以任意地进行处理器模式切换。
用户模式下是不允许模式切换的。
异常模式:是指除用户模式和系统模式以外地5种模式,常用于处理中断。
异常模式有:●FIQ(Fast Interrupt ReQuest)●IRQ(Interrupt ReQuest)●管理svc(Supervisor)●中止abt(Abort)●未定义und(Undefined)在特定地异常出现时,进入相应的模式。
某种模式都有附加的寄存器,以避免出现异常时用户模式的状态不可靠。
在软件的控制下可以改变模式,外部中断和异常也可以引起模式发生改变。
大多数应用程序在用户模式下执行。
当处理器工作在用户模式时,正在执行的程序不能访问某些被保护的处理器资源,也不能改变模式,除非异常(Exception)发生。
这允许适当编写操作系统来控制系统资源的使用。
ARM9体系结构的异常类型和异常处理模式(如表2)表2②状态寄存器CPSR/SPSR以及对其进行的访问●状态寄存器CPSR和SPSR:包含了条件码标志,中断禁止位,当前处理器模式以及其它状态和控制信息。
每种异常都有一个程序状态保存寄存器SPSR,当异常出现时SPSR用于保存CPSR的状态值。
CPSR和SPSR的格式如下:状态位:位31-28依次为N、Z、C、V分别表示符号位Negative、零位Zero、进位位Carry和溢出位Overflow。
ARM 设置用户程序的堆栈
ARM 设置用户程序的堆栈arm设置用户程序的堆栈1.函数:提供编译器的初始化C库函数,设置用户程序堆栈所需的堆栈字母息。
2._uu用户u初始uu堆栈()返回:r0中的堆基址? R1中的堆栈基址,即堆栈区域中的最高地址?r2中的堆限制? R3中的堆栈限制,即堆栈区域中的最低地址。
?有单区模型和双区模型。
单区模型:(r0,r1)是单个堆栈和堆区。
r1大于r0,并忽略r2和r3。
R0--R1是由堆和堆栈共享的内存区域。
堆从R0增长,堆栈从R1下降。
双区模型:(r0,r2)是初始堆,(r3,r1)是初始堆栈。
r2大于或等于r0。
r3小于r1。
堆和堆栈指定不同的内存区域。
3.如果不使用分散加载文件,则__user_initial_stackheap()必须由用户自己实现。
实施示例:;userinitialstack&heap面积|。
文本|,代码,只读import__use_two_region_memoryexport__user_initial_stackheap__user_initial_stac kheapldrr0,=heap_umemldrr1,=(stack_mem+usr_stack_size)ldrr2,=(heap_mem+heap_size)ldrr3,=stack_membx lr4.分布式加载可以由用户实现,也可以在分布式加载描述文件中定义两个特殊的执行区域(双区域模型,单区域模型仅定义arm_lib_stackheap):arm_lib_uuuheap和arm_lib_uuuuustack(该区域具有空属性),因此,使用C库的默认实现。
在这种情况下,它将导致库选择一个使用以下符号值的库:image$$arm\ulib\uuheap$$base、image$$arm\ulib\uheap$$zi$$limit、image$$arm\ulib\ustack$$base和image$$arm\ulib\ustack$$zi$$limit。
堆栈操作指令(StackOperationInstruction)
堆栈是⼀个重要的数据结构,它具有“先进后出”的特点,通常⽤来保存程序的返回地址。
它主要有两⼤类操作:进栈操作和出栈操作。
1)、进栈操作、PUSH(Push Word or Doubleword onto Stack)指令格式:PUSH Reg/MemPUSH Imm ;80286+⼀个字进栈,系统⾃动完成两步操作:SP←SP-2,(SP)←操作数;⼀个双字进栈,系统⾃动完成两步操作:ESP←ESP-4,(ESP)←操作数。
、PUSHA(Push All General Registers)指令格式:PUSHA ;80286+其功能是依次把寄存器AX、CX、DX、BX、SP、BP、SI和DI等压栈。
、PUSHAD(Push All 32-bit General Registers)指令格式:PUSHAD ;80386+其功能是把寄存器EAX、ECX、EDX、EBX、ESP、EBP、ESI和EDI等压栈。
2)、出栈操作、POP(Pop Word or Doubleword off Stack)指令格式:POP Reg/Mem弹出⼀个字,系统⾃动完成两步操作:操作数←(SP),SP←SP-2;弹出⼀个双字,系统⾃动完成两步操作:操作数←(ESP),ESP←ESP-4。
、POPA(Pop All General Registers)指令格式:POPA ;80286+其功能是依次把寄存器DI、SI、BP、SP、BX、DX、CX和AX等弹出栈。
其实,程序员不⽤记住它们的具体顺序,只要与指令PUSHA对称使⽤就可以了。
、POPAD(Pop All 32-bit General Registers)指令格式:POPAD ;80386+其功能是依次把寄存器EDI、ESI、EBP、ESP、EBX、EDX、ECX和EAX等弹出栈,它与PUSHAD对称使⽤即可。
7、转换指令XLAT(Translate Instruction)转换指令有两个隐含操作数BX和AL。
基于arm的操作系统中断堆栈分析及实现[1]
第26卷 第1期2004年1月武 汉 理 工 大 学 学 报JOURNAL OF W UHAN UN IVERSIT Y OF TECHNOLOG Y V o l .26 N o.1 Jan .2004基于A RM 的操作系统中断堆栈分析及实现叶庆云,刘 奇(武汉理工大学信息工程学院,武汉430070)摘 要: 嵌入式实时操作系统多任务实现的基础就是系统对中断的处理。
首先介绍了多任务实时操作系统的多任务实现原理及A RM 芯片的特点,在此基础上以A RM 芯片为例,详细分析了在发生中断时如何实现中断现场的保护以及一般嵌入式操作系统的多任务切换过程,并列出代码给予说明。
关键词: 操作系统; 中断; 堆栈中图分类号: T P 316.2文献标识码: A 文章编号:167124431(2004)0120087203收稿日期:2003210208.作者简介:叶庆云(19572),女,副教授.E 2m ail :yeqinyun @yahoo .com .cn在进行嵌入式开发时,多任务实时嵌入式操作系统作为上层应用平台而得到广泛应用。
嵌入式操作系统的多种芯片的移植也就成了热门话题,在嵌入式操作系统的移植中,系统的大多数功能都是由C 语言实现的。
可是在任务上下文保护这块则完全是由汇编完成。
如果不能理解其原理、过程和实现方法就不能顺利完成系统的移植。
而A RM 系统,由于其各个状态对寄存器的保护和屏蔽更加重了工作难度。
因此,针对A RM 芯片进行分析并实现了系统移植中的任务上下文保护。
1 多任务实现的原理在多任务系统中,为了实现多任务切换、中断嵌套和中断后任务恢复,必须在发生中断时保护中断的现场,把中断产生时各个寄存器里的数值全都保存到任务堆栈中去,在中断退出时从堆栈里恢复各个寄存器[1]。
典型的R TO S 在处理中断时都会有这样的操作。
在发生中断时从中断向量表跳转后,首先保存中断现场的寄存器到目前任务的堆栈中,然后执行一些与O S 相关的操作,最后才执行中断服务程序。
工作随记之——ARM内存,程序空间及堆栈操作
工作随记之——ARM内存,程序空间及堆栈操作ARM内存,程序空间及堆栈操作1.ARM内存可任意读写,程序空间只可读不可写,不然会出现硬件错误。
(注意:在使用指针时,一定要分配空间,不然也会进入硬件异常)unsigned int addr,temp,*data;data=&temp;//重要addr = 0x0000000; //地址可以是内存、程序空间、可读寄存器*data=*(unsigned int*)addr;*(unsigned int*)addr=*data; //适用于内存、可写寄存器,不可对程序空间进行此操作2.内存堆栈的设置。
堆:程序执行后申请的内存空间。
栈:一般用作函数调用时,把返回信息压入栈中,函数执行完成后恢复现场。
(注意:在函数中使用变量,会消耗栈的空间,而不是堆的空间。
所以要合理设置变量跟栈空间大小。
)采用分散加载文件方式设置堆栈空间大小HEAP +0x0 UNINIT //紧跟在程序初始化变量后面,最大空间利用。
向上增长{stack.o (heap)}STACK 0x10008000 UNINIT //内存的最高地址。
最后空间利用。
向下增长(LPC1766){stack.o (stack)}heap.s stack.s文件:IMPORT __use_two_region_memoryEXPORT __user_initial_stackheapAREA stack, DATA, NOINITuser_stack ; SPACE 0X1000AREA Heap, DATA, NOINITbottom_of_heap ; SPACE 0X4000AREA rt_heap_stack, CODE, READONL YENTRY__user_initial_stackheap ;单区LDR R0, = bottom_of_heap ;堆基地址LDR R1, = user_stack ;栈基地址(内存最高地址,向下增长)LDR R2, = user_stack ;堆极限地址LDR R3, = bottom_of_heap ;栈极限地址BX LRALIGN ;对齐END。
gcc arm汇编 堆栈定义
gcc arm汇编堆栈定义在ARM汇编语言中,堆栈是一种非常重要的概念,用于保存程序执行过程中的临时数据和返回地址等信息。
在ARM架构中,堆栈是由系统自动管理的,而程序员主要需要了解如何定义和使用堆栈。
在ARM汇编中,堆栈的定义需要使用一对特殊的寄存器,分别为堆栈指针寄存器(Stack Pointer Register,SP)和基址寄存器(Frame Pointer Register,FP)。
SP用于保存堆栈的当前顶部地址,FP用于保存当前堆栈帧的基地址。
定义堆栈的关键指令是PUSH和POP,用于将数据从寄存器压入堆栈或从堆栈弹出到寄存器。
以下是一些常用的ARM汇编指令和相关定义:1. PUSH指令:将数据从寄存器压入堆栈,一般用于保存函数调用时需要保护的寄存器。
```PUSH {R0-R3} ; 将寄存器R0-R3的值从高地址向低地址依次压入堆栈```2. POP指令:将数据从堆栈弹出到寄存器,一般用于恢复函数调用前需要保护的寄存器。
```POP {R0-R3} ; 将堆栈中的值从低地址向高地址依次弹出到寄存器R0-R3```3. STMFD指令:将多个寄存器的值连续存储到堆栈中,以FP 为基地址。
```STMFD SP!, {R4-R7} ; 将寄存器R4-R7的值从高地址向低地址依次存储到以SP为基址的堆栈中,并更新SP的值```4. LDMFD指令:从堆栈中连续读取多个寄存器的值,以FP为基地址。
```LDMFD SP!, {R4-R7} ; 将以SP为基址的堆栈中的值从低地址向高地址依次读取到寄存器R4-R7,并更新SP的值```除了上述指令外,还可以使用MOV指令将堆栈指针寄存器的值保存到基址寄存器中,或将基址寄存器的值恢复到堆栈指针寄存器中。
在ARM汇编中,堆栈的定义和使用非常灵活,可以根据需要来决定何时保存和恢复寄存器的值。
在函数调用时,一般会先使用PUSH指令将需要保护的寄存器保存到堆栈中,然后再使用STMFD指令将更多的寄存器保存到堆栈中。
关于ARM堆栈的一些问题
关于ARM堆栈的一些问题
学习ARM 寻址方式的时候,里面有种寻址为堆栈寻址。
ARM7 中的寄存
器R13 就是作为堆栈指针SP 的。
堆栈内容的增减,就会影响堆栈指针的移动。
这个移动方向与堆栈的生长方向有关。
ARM7 的有两种堆栈生长方向。
1.向上生长在像堆栈写入数据后,堆栈指针的值变大,也就是向高地址方向生长,这种是递增堆栈。
2.向下生长在像堆栈写入数据后,堆栈指针的值变小,也就是向低地址方向生长,这种是递减堆栈。
要注意的问题不仅仅是堆栈增长方向。
还要考虑到堆栈指针指向的存储单元
是否已经保存有堆栈数据,或者说入栈时是否可以直接向堆栈指针指向的存储
单元写入数据。
这就是满堆栈与空堆栈。
1.满堆栈堆栈指针指向最后压入栈的有效数据项,称为满堆栈。
此时堆栈入栈操作要先调整指针再写入数据;
2.空堆栈堆栈指针指向下一个待压入数据的空位置,称为空堆栈。
此时堆栈的入栈操作的入栈操作要先先写入数据再调整指针。
综合以上叙述,就可以得到四种堆栈类型,分别为满递增,空递增,满递减,空递减。
然后我想到了uc/OS-II 中就有与堆栈类型密切相关的东西,移植ucOS-II 到相应平台上,就要考虑到堆栈类型。
我看书上说到ADS1.2 堆栈类型只支持满
递减。
此时我的问题就出来了,堆栈类型具体应该是跟MCU 有关,而不同的
编译器又支持不同的类型,是不是这样呢,最后我在ADS1.2 文件的一个讲ATPCS 的PDF 中找到了答案,原来就是这样,ADS1.2 只能满递减。
一下就是。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
arm堆栈操作arm堆栈的组织结构是满栈降的形式,满栈即sp是要停留在最后一个进栈元素,降:就是堆栈的增长方向是从高地址向低地址发展。
arm对于堆栈的操作一般采用LDMFD (pop)和STMFD (push) 两个命令。
以前困惑的就是STMFD 命令对于操作数是按照什么顺序压栈的比如:STMFD sp!{R0-R5,LR} 进栈顺序是:高地址(1方式)LRR5R4```````R0 <-sp低地址还是:高地址(2方式)R0R1```R5LR <-sp低地址现在通过下表,可以轻松的解决这个问题:按照图表,可知STMFD对应的是STMDB,根据arm指令手册,可知STMDB入栈顺序是(1方式)而LDMFD对应的是LDMIA,这样这两个操作就可以成功配对:以下是我在学习ARM指令中记录的关于堆栈方面的知识1、寄存器R13 在ARM 指令中常用作堆栈指针2、对于R13 寄存器来说,它对应6个不同的物理寄存器,其中的一个是用户模式与系统模式共用,另外5个物理寄存器对应于其他5种不同的运行模式。
采用以下的记号来区分不同的物理寄存器:R13_<mode> 其中,mode为以下几种模式之一:usr、fiq、irq、svc、abt、und。
3、寄存器R13在ARM指令中常用作堆栈指针,但这只是一种习惯用法,用户也可使用其他的寄存器作为堆栈指针。
而在Thumb指令集中,某些指令强制性的要求使用R13作为堆栈指针。
由于处理器的每种运行模式均有自己独立的物理寄存器R13,在用户应用程序的初始化部分,一般都要初始化每种模式下的R13,使其指向该运行模式的栈空间,这样,当程序的运行进入异常模式时,可以将需要保护的寄存器放入R13所指向的堆栈,而当程序从异常模式返回时,则从对应的堆栈中恢复,采用这种方式可以保证异常发生后程序的正常执行。
4、有四种类型的堆栈:堆栈是一种数据结构,按先进后出(First In Last Out,FILO)的方式工作,使用一个称作堆栈指针的专用寄存器指示当前的操作位置,堆栈指针总是指向栈顶。
当堆栈指针指向最后压入堆栈的数据时,称为满堆栈(Full Stack),而当堆栈指针指向下一个将要放入数据的空位置时,称为空堆栈(Empty Stack)。
同时,根据堆栈的生成方式,又可以分为递增堆栈(Ascending Stack)和递减堆栈(DecendingStack),当堆栈由低地址向高地址生成时,称为递增堆栈,当堆栈由高地址向低地址生成时,称为递减堆栈。
这样就有四种类型的堆栈工作方式,ARM 微处理器支持这四种类型的堆栈工作方式,即:◎Full descending 满递减堆栈堆栈首部是高地址,堆栈向低地址增长。
栈指针总是指向堆栈最后一个元素(最后一个元素是最后压入的数据)。
ARM-Thumb过程调用标准和ARM、Thumb C/C++ 编译器总是使用Full descending 类型堆栈。
<这是什么原因呢?>◎Full ascending 满递增堆栈堆栈首部是低地址,堆栈向高地址增长。
栈指针总是指向堆栈最后一个元素(最后一个元素是最后压入的数据)。
◎Empty descending 空递减堆栈堆栈首部是低(这里是不是错了,应该是高地址吧)地址,堆栈向高地址增长。
栈指针总是指向下一个将要放入数据的空位置。
◎Empty ascending 空递增堆栈堆栈首部是高地址,堆栈向低地址增长。
栈指针总是指向下一个将要放入数据的空位置。
5、操作堆栈的汇编指令堆栈类型入栈指令出栈指令Full descending STMFD (STMDB) LDMFD (LDMIA) Full ascending STMFA (STMIB) LDMFA (LDMDA) Empty descending STMED (STMDA) LDMED (LDMIB) Empty ascending STMEA (STMIA) LDMEA (LDMDB)例子:STMFD r13!, {r0-r5} ; Push onto a Full Descending Stack LDMFD r13!, {r0-r5} ; Pop from a Full Descending Stack.例子1)保护现场参数,不影响PC,嵌汇编的时候对之前的存参数的寄存器R0~R12保存STMFD r13!, {r0-r7,LR}LDMFD r13!, {r0-r7,PC}2) ARM汇编中lr(r14)寄存器的作用lr(r14)的作用问题,这个lr一般来说有两个作用:1.当使用bl或者blx跳转到子过程的时候,r14保存了返回地址,可以在调用过程结尾恢复。
2.异常中断发生时,这个异常模式特定的物理R14被设置成该异常模式将要返回的地址。
另外注意pc,在调试的时候显示的是当前指令地址,而用mov lr,pc的时候lr保存的是此指令向后数两条指令的地址,大家可以试一下用mov pc,pc,结果得到的是跳转两条指令,这个原因是由于arm的流水线造成的,预取两条指令的结果.3.我们看到的LR值是上一个子程序调用保存的子程序返回地址,这个LR是要赋给PC 的。
嵌入式汇编要手动保存返回地址,进行现场保护。
PC记录当前运行的地址。
下一条回自己+4进入子程序,LR才自动更新为返回地址值,PC为程序运行地址ARM汇编嵌套子程序几个星期前阅读了(加)Carl Hamacher、Zvonko Vranesic、Safwat Zaky编写的《计算机组成》第五版中的ARM子程序调用的一些知识,启发很大,顺便将它整理了一下并加入了自己的理解。
子程序1 通过寄存器传递参数BL指令通常用于调用一个子程序。
它和B 指令的区别在于它将返回地址装载到R14中。
由于子程序可能是嵌套的,因此LR的内容必须保存在子程序所使用的堆栈中。
下面的例子使用寄存器传递参数。
调用者通过寄存器R1和R2分别将数组的大小和数组的首地址传递给子程序;子程序利用寄存器R0将和传递给调用者。
该子程序使用了寄存器R3,必须将它和LR推入堆栈。
调用程序LDR R1, NLDR R2, POINTERBL LISTADDSTR R0, SUM…子程序LISTADD STMFD R13!, {R3, R14}MOV R0, #0LOOP LDR R3, [R2], #4ADD R0, R0, R3SUBS R1, R1, #1BGT LOOPLDMFD R13!, {R3, R15}注:这里并没有遵守APCS(ARM过程调用标准),一般由调用者负责保存R0~R3,被调用者负责保存其他的寄存器以使调用返回后程序的状态不被破坏。
2 通过堆栈传递参数调用程序LDR R0, POINTERSTR R0, [R13, #-4]! ;将数组首地址推入堆栈LDR R0, NSTR R0, [R13, #-4]! ;将元素个数N推入堆栈BL LISTADDLDR R0, [R13, #4] ;将元素和装载到寄存器R0中STR R0, SUMADD R13, R13, #8 ;恢复堆栈…子程序LISTADD STMFD R13!, {R0-R3, R14} LDR R1, [R13, #20]LDR R2, [R13, #24]MOV R0, #0LOOP LDR R3, [R2], #4ADD R0, R0, R3SUBS R1, R1, #1BGT LOOPSTR R0, [R13,#24] ;把和推入堆栈的最深处LDMFD R13!, {R0-R3, R15}3 嵌套子程序当子程序嵌套时,堆栈是用于处理返回地址的最合适的数据结构。
当调用子程序时在堆栈上建立了完整的堆栈结构。
应当注意当前子程序的堆栈帧指针所指向的空间中存储的是调用当前子程序的子程序的堆栈帧指针。
调用者将子程序所需要的参数按照顺序推入堆栈。
子程序首先保存工作寄存器、调用者的堆栈帧指针以及返回地址,然后它计算自己的堆栈帧指针的值(ADD FP, SP, #16),并利用这个堆栈帧指针从堆栈帧中获取调用者传递给它的参数。
在子程序完成它的任务之后,它也将返回值保存在堆栈中,此例保存在参数所在的内存单元。
调用者和被调用者必须约定好参数的传递顺序和返回值保存位置。
如果返回值比较多的话,调用者要为返回值预先在堆栈中保留合适的空间。
调用程序2000 LDR R0, PARAM2STR R0, [SP, #-4]! ;将参数推入堆栈LDR R0, PARAM1STR R0, [SP, #-4]!BL SUB12020 LDR R0, [SP] ;保存SUB1的结果STR R0, RESULTADD SP, SP, #8 ;恢复堆栈…子程序2100 SUB1 STMFD SP!, {R0-R3, FP,LR}ADD FP, SP, #16 ;计算帧指针LDR R0, [FP, #8] ;载入参数1LDR R1, [FP, #12] ;载入参数2…LDR R2, PARAM3 ;载入参数3STR R2, [SP, #-4]! ;将参数3推入堆栈BL SUB22164 LDR R2, [SP], #4 ;将SUB2的结果弹出并存储在R2中,并递增SP…STR R3, [FP, #8] ;将结果推入堆栈LDMFD SP!, {R0-R3, FP, PC} ;恢复寄存器并返回3000 SUB2 STMFD SP!, {R0, R1, FP, LR}ADD FP, SP, #8 ;载入结构指针LDR R0, [FP, #8] ;载入参数…STR R1, [FP, #8] ;将结果推入堆栈LDMFD SP!, {R0, R1, FP, PC}3 子程序编译后都放在哪嵌汇编子程序:会在编译后统一放到一个地方系统调用__main()库函数,后再进入main.c前的初始化C下面的堆栈命令前(这个堆栈是用来放置C里参数的)嵌汇编子程序:按定义顺序放在堆栈前,而C语言子程序,放在初始化堆栈堆栈命令后。