【教学课件】第9章ARM程序设计.ppt
第9章 ARM程序设计,ARM常用开发环境,主要分为基于Windows平台的和基于Linux平台的两大类 基于Windows平台ADS,目前已经基本被替代RealView Developer Suite(RVDS),目前主流工具Embedded Workbench for ARM(EWARM),IAR System公司,入门简单,授权费用高RealView Microcontroller Development Kit(MDK),源自德国Keil公司,多用于低端ARM处理器开发基于Linux平台ARM-Linux-GCC,GNU开发的编译器集,依赖于不同的目标机的平台,使用繁琐但不需要授权费用,RVDS开发环境简介,替代ADS的新一代开发环境集程序的编辑、编译及调试于一体 支持软件仿真和硬件调试 持汇编、C和C+等多种源代码的编译 目前广泛的应用于ARM处理器开发包括CodeWarrior IDE集成开发环境和RVDebugger调试器两部分,CodeWarrior IDE的主窗口,RVD调试器主窗口,汇编语言程序设计特点,汇编语言依赖于机器硬件,不同CPU提供的汇编指令可能有很大的不同,因此汇编源程序几乎不具有移植性;但汇编语言程序速度快、效率高,更能发挥机器硬件的长处;2.汇编程序需要用户将汇编过程中需要的一些信息明确地写入源程序,如:内存逻辑段的划分情况、数据在内存中的存放情况,等等。这些信息的说明由汇编程序指定相应的伪指令来完成,并不由CPU定义;3.伪指令 是在汇编过程中执行的,因而不会在机器语言程序中产生目标代码;只有CPU定义的(助记符)指令才会生成目标代码,并在程序运行过程中执行;,6/52,4.源程序经汇编得到的目标代码实际上也是不能运行的,还要经过系统的链接定位后才能生成真正的可执行文件(.EXE文件)。即一般用汇编语言开发应用软件时应依次 完成以下几个步骤:(1)编辑得到源程序(2)汇编得到目标代码(解决语法错误)(3)链接得到可执行文件(解决定位错误)(4)调试得到功能正确的应用软件(解决逻辑错误)(以上第24步任何一步有错都应返回到第1步重来),7/52,符号定义伪指令,*,8/52,GBLA Test1;声明全局数字变量Test1,赋值为0 xaa Test1 SETA 0 xaaGBLL Test2;声明全局逻辑变量Test2,赋值为“真”Test2 SETL TRUEGBLS Test3;声明全局字符串变量为Test3,赋值为Testing“Test3 SETS Testing“LCLA Test4;声明局部数字变量Test4,赋值为0 xaa Test3 SETA 0 xaaLCLL Test5;声明局部逻辑变量Test5,赋值为“真”Test4 SETL TRUELCLS Test6;声明局部字符串变量Test6,赋值为Testing“Test6 SETS Testing“RegList RLIST R0-R5,R8,R10;声明寄存器列表RegList,LDM/STM指令可通过该名称访问寄存器列表,*,9/52,数据定义伪指令,*,Str DCB“This is a test!”;分配一片连续的字节存储单元并初始化Test2Data DCW 1,2,3;分配一片连续的半字存储单元并初始化DataTest DCD 4,5,6;分配一片连续的字存储单元并初始化FdataTest DCFD 2E115,-5E7;分配一片连续的字存储单元并初始化FdataTest DCFS 2E5,-5E-7;分配一片连续的字存储单元并初始化DataTest DCQ 100;分配一片连续的8字节存储单元并初始化DataSpace SPACE 100;分配连续100个字节存储单元并初始化为0,定义一个首址为4096(0 x1000)的内存表,该表中包含5个字段:A(4Bytes)、B(4Bytes)、X(8Bytes)、Y(8Bytes)、String(256Bytes)。MAP 0 x1000;内存表首地址的值为0 x1000A FIELD 4;定义A的长度为4字节,位置为0 x1000B FIELD 4;定义B的长度为4字节,位置为0 x1004X FIELD 8;定义X的长度为8字节,位置为0 x1008Y FIELD 8;定义Y的长度为8字节,位置为0 x1010String FIELD 256;定义String为256字节,位置为0 x1018LDR R6,A,基于绝对地址的内存表,仅可访问指令前/后4KB地址范围的数据字段,双精度,单精度,定义一个内存表,其首址为固定地址与R9和,表中包含同样字段。MAP 0,R9;内存表首地址为0与R9寄存器内容的和A FIELD 4;定义A的长度为4字节,相对位置为0B FIELD 4;定义B的长度为4字节,相对位置为4X FIELD 8;定义X的长度为8字节,相对位置为8YFIELD 8;定义Y的长度为8字节,相对位置为16String FIELD 256;定义String为256字节,相对位置为24ADR R9,DATASTART;伪指令ADR 初始化R9 LDR R5,B;相当于LDR R5,R9,#4,可访问地址范围超过4 KB的数据,基于相对地址的内存表,定义一个内存表,其首址为PC的值,表中包含同样字段。Dstruc SPACE 280;分配280个字节单元MAP Dstruc;内存表首地址为DstrucA FIELD4;定义A的长度为4字节,相对位置为0B FIELD4;定义B的长度为4字节,相对位置为4X FIELD8;定义X的长度为8字节,相对位置为8Y FIELD8;定义Y的长度为8字节,相对位置为16String FIELD256;定义String为256字节,相对位置为24LDR R5,B;相当于LDR R5,PC,#4,基于PC的内存表,可访问地址范围不超过4 KB的数据,汇编控制伪指令,GBLA Counter;声明全局的数字变量Counter CounterSETA 3;由变量Counter控制循环次数WHILE Counter 10指令序列修改Counter的值WEND,*,13/52,宏和宏定义指令,MACRO$标号 宏名$参数1,$参数2,宏体MEND MACRO和MEND伪指令可以嵌套使用。宏的使用方式和功能与子程序有些相似。子程序可以提供模块化的程序设计、节省存储空间并提高运行速度,但在使用子程序结构时需要保护现场,从而增加了系统的开销。因此,在代码较短且需要传递的参数较多时,可以使用宏指令代替子程序。,*,14/52,定义一条宏指令,使其可以完成测试-跳转操作。MICRO;宏定义开始$lable TestAndBranch$dest,$reg,$cc$lable CMP$reg,#0 B$cc$destMEND;宏定义结束,宏名,宏参,目标地址,测试寄存器,条件,用于构造宏义体内的标号,test TestAndBranch NonZero,R0,NE;程序中的宏调用NonZero,test CMP R0,#0;程序汇编时宏展开BNE NonZero;其它指令序列NonZero;其它指令序列,*,15/52,MICRO;宏定义开始$lable xmac$p1,$p2;宏的名称为xmac,有两个参数$p1和$p2$lable.loop1;$lable.loop1为宏定义体的内部标号 BGE$lable.loop1$lable.loop2 指令;$lable.loop2为宏定义体的内部标号 BL$p1;参数$p1为一个子程序的名称 BGT$lable.loop2 ADR$p2MEND;宏定义结束,abc xmac subr1,de;宏调用,其中宏标号为abc,参数为subr1,de;宏展开abc.loop1 BGE abc.loop1abc.loop2 BL subr1 BGT abc.loop2 ADR de,16/52,其它常用伪指令,*,17/52,AREA、ENTRY、END伪指令,AREA 段名属性1,属性2,定义代码段或数据段 1.段名若以数字开头,则该段名需用“|”括起来,如|1_test|。2.属性字段表示该代码或数据的相关属性,多个属性关键字以逗号分隔:CODE:用于定义代码段。DATA:用于定义数据段。READONLY:指定本段为只读属性,代码段默认为READONLY。READWRITE:指定本段为可读写属性,数据段默认为READWRITE。ALIGN 表达式:表达式取值为031。ELF(可执行连接文件)的段默认按字对齐。COMMON:定义一个通用段,各源文件中同名的COMMON段共享同一段存储单元。,一个汇编源程序至少包含一个段,程序太长时可分为多个段并用AREA标示;一个汇编源程序最多只能有一个(可以没有)ENTRY标示程序入口;一个完整的汇编源程序中至少应有一个用ENTRY标示的入口,如有多个则程序的真正入口点由连接器指定;一个汇编源程序用END标示源文件结束;,AREA Init,CODE,READONLY;代码段Init属性为只读 ENTRY;指定应用程序的入口点指令序列END;通知编译器源程序结束,18/52,ALIGN伪指令,ALIGN 表达式,偏移量 ALIGN伪指令可通过添加填充字节的方式,使当前位置满足一定的对其方式。其中:表达式可能的取值为2的幂(如1,2,4,8和16等)。若未指定表达式则将当前位置对齐到下一个字的位置。若使用偏移量字段,则当前位置的对齐方式为:表达式+偏移量。AREA Init,CODE,READONLY,ALIEN=3;指定后面的指令;为8字节对齐 指令序列END,*,19/52,CODE16、CODE32伪指令,在ARM指令和Thumb指令混合编程的代码里,该伪指令用于通知编译器其后的指令序列为16位还是32位。注意它们只能控制编译方式,并不能切换处理器状态。,AREA Init,CODE,READONLYCODE32;通知编译器其后为32位的ARM指令 LDR R0,=NEXT+1;将跳转地址放入寄存器R0 BX R0;程序跳转,并将处理器切换到Thumb状态CODE16;通知编译器其后为16位的Thumb指令NEXT LDR R3,=0 x3FFEND;程序结束,20/52,EQU伪指令,名称 EQU 表达式,类型 用于为程序中的常量和标号等定义一个等效的字符名称,类似于C语言中的define。其中EQU可用“*”代替。当表达式为32位的常量时,其数据类型可以为CODE16,CODE32或DATA三种类型之一。Test EQU 50;定义常量Test的值为50Addr EQU 0 x55,CODE32;定义标号Addr的值为0 x55,且该处为32位的ARM指令,*,21/52,EXPORT(或GLOBAL)标号WEAKIMPORT(或EXTERN)标号WEAK EXPORT用于声明一个全局标号,该标号可在其他文件中引用。WEAK选项声明其他同名标号优先于该标号被引用。IMPORT用于通知编译器当前源文件可能要引用其他源文件中定义的标号。WEAK选项表示所有源文件都没有定义这个标号时编译器不会给出错误信息,在多数情况下将该标号置为0。若该标号被B或BL指令引用,则将B或BL指令置为NOP操作。使用IMPORT则标号一定会被加入到当前源文件的符号表中。使用EXTERN则标号未被引用时不会被加入到当前源文件的符号表中。标号在程序中区分大小写。AREA Init1,CODE,READONLYEXPORT Main;源文件1声明一个可全局引用的标号Main END AREA Init2,CODE,READONLYIMPORT Main;源文件2需要引用在别处定义的标号Main END,22/52,INCLUDE(或GET)、INCBIN伪指令,INCLUDE 文件名INCBIN 文件名INCLUDE(或GET)伪指令用于将一个源文件包含到当前的源文件中。被包含的源文件在当前位置进行汇编处理。INCBIN伪指令用于将一个目标文件或数据文件包含到当前的源文件中 被包含的文件不作任何变动,编译器从其后开始继续处理。AREA Init,CODE,READONLY GET C:a2.s;通知编译器此处插入源文件C:a2.s INCBIN a1.dat;通知编译器此处插入数据文件a1.dat INCBIN C:a2.o;通知编译器此处插入目标文件C:a2.o END,编写汇编源程序时,常将一个源文件专门用于定义宏指令(MICRO)、符号常量(EQU),结构化数据类型(MAP和FIELD),然后再用GET伪指令将这个源文件包含到其他源文件中。,汇编语言中常用的符号,在汇编语言程序设计中,经常使用各种符号代替地址、变量和常量等,以增加程序的可读性。尽管符号的命名由编程者决定,但并不是任意的,必须遵循以下的约定:符号区分大小写,同名的大小写字母符号会被编译器认为是两个不同的符号;局部标号可以以数字开头,其他标号均不得以数字开头;符号在其作用范围内必须唯一;自定义的符号名不能与系统的保留字相同;符号名不应与指令或伪指令同名。,常 量,常量是指其值在程序的运行过程中不能被改变的量ARM(Thumb)汇编程序所支持的常量有数字常量、逻辑常量和字符串常量。数字常量一般为32位的整数,用“#”标识。当作为无符号数时,其取值范围为0232-1 当作为有符号数时,其取值范围为-231231-1逻辑常量只有两种取值情况:真TURE或假FAUSE。字符串常量为一个固定的字符串,一般用于程序运行时的信息提示。,*,25/52,变 量,ARM(Thumb)汇编程序支持数字变量、逻辑变量和字符串变量。可用GBLA,GBLL和GBLS伪指令声明全局变量,用LCLA,LCLL和LCLS伪指令声明局部变量,并可使用SETA,SETL和SETS对其进行初始化。变量可通过代换操作(“$”)获得一个常量:如果数字变量前加“$”,编译器会将该数字变量的值转换为十六进制的字符串,并用该十六进制的字符串代换“$”后的数字变量。如果逻辑变量前加“$”,编译器会将该逻辑变量代换为其值(真或假)。如果字符串变量前加“$”,编译器会用其值代换“$”后的字符串变量。LCLS S1;定义局部字符串变量S1和S2LCLS S2S1 SETS Test!S2SETS This is a$S1;S2的值为This is a Test!”,26/52,常用运算符和表达式,在汇编语言程序设计中,经常会使用各种表达式。表达式常用于各种变量的运算,一般由变量、常量、运算符和括号构成。常用表达式有数字表达式、逻辑表达式和字符串表达式,其运算次序遵循如下优先级。优先级相同的双目运算符的运算顺序为从左到右;相邻的单目运算符的运算顺序为从右到左,且单目运算符的优先级高于其他运算符;括号运算符的优先级最高。,算术/逻辑表达式及运算符,算术/逻辑表达式由数字/逻辑变量、数字/逻辑常量、运算符和括号构成。,运算次序的规定:优先级相同的双目运算符的运算顺序为从左到右;相邻的单目运算符的运算顺序为从右到左,且单目运算符的优先级高于其他运算符;括号运算符的优先级最高。,28/52,注意:这些运算在汇编过程中计算,机器码中出现的已经是表达式的值了。,字符串表达式及运算符,字符串表达式一般由字符串常量、字符串变量、运算符和括号构成。编译器支持的字符串最大长度为512字节。LEN:X 返回字符串X的长度(字符数)。CHR:M 将0255之间的整数M转换为一个字符。STR:X 将数字或逻辑表达式X转换为一个字符串。对于数字表达式,STR运算得到一个以十六进制字符组成的字符串;对于逻辑表达式,STR运算得到字符串“T”或“F”。X:LEFT:Y 返回字符串X左端的一个子串。整数Y表示要返回的字符个数。X:RIGHT:Y 返回字符串X右端的一个子串。整数Y表示要返回的字符个数 X:CC:Y 将字符串Y连接到字符串X的后面形成一个新字符串。,*,29/52,其它常用运算符,?X返回定义符号X的代码行所生成的可执行代码的长度(字节数):DEF:X 判断是否定义了符号X:如果符号X已经定义则结果为真,否则为假。,BASE:X 返回基于寄存器的表达式X中寄存器的编号。INDEX:X 返回基于寄存器的表达式X中相对于其基址寄存器的偏移量。,*,30/52,ARM汇编语言程序结构,ARM(Thumb)汇编语言程序中,以程序段(代码段和数据段)为单位组织代码。一个汇编程序至少应该有一个代码段。当程序较长时,可以分割为多个代码段和数据段,多个段在程序编译连接时最终形成一个可执行的映象文件。可执行映象文件通常由以下几部分构成:一个或多个代码段,代码段的属性默认为READONLY。零个或多个包含初始化数据的数据段,数据段的属性默认为READWRITE。零个或多个不包含初始化数据的数据段,数据段的属性为默认为READWRITE。,*,31/52,ARM汇编语言程序结构示例,GET option.sGET addr.s AREA Init,CODE,READONLY ENTRY spr MULr1,r0,r0 AREAData1,DATA,READWRITE numDCD10 END,引用其它源文件,代码段,数据段,定义代码段,指定程序入口,程序主体,ARM汇编程序设计实例,重点介绍如何用ARM汇编语言实现:顺序结构分支结构循环结构子程序调用与返回,顺序结构-两个64位数相加,AREA add64,CODE,READONLYENTRYstartLDRR0,=data1;R0中保存data1的首地址LDRR1,R0;用寄存器间接寻址方式读数据1的高32位到R1LDRR2,R0,#4;用寄存器间接寻址方式读数据1的低32位到R2LDRR0,=data2;R0中保存data2的首地址LDRR3,R0;用寄存器间接寻址方式读数据2的高32位到R3LDRR4,R0,#4;用寄存器间接寻址方式读数据1的低32位到R4ADDSR6,R2,R4;低32位相加,并影响标志位,保存进位ADCR5,R1,R3;高32位相加,并使用标志位CLDRR0,=result;R0中保存result的首地址STRR5,R0;保存结果的高位STRR6,R0,#4;保存结果的低位data1DCD0 x11223344,0 xFFDDCCBBdata2DCD0 x11223344,0 xFFDDCCBBresult DCD 0,0END,例9.1在RVDS上的运行结果,分支结构-“if else”结构,AREA add64,CODE,READONLYENTRYStart LDRR0,data1;R0中保存data1 LDRR1,data2;R1中保存data2 CMPR0,R1;比较R1和R0中的值的大小 BHIsave;R0R1则跳转到标号为save处 MOVR0,R1;将R1的值赋给R0Save STRR0,result;将结果保存到resultdata1DCD0 x100data2DCD0 x200result DCD0END,例9.3在RVDS上的运行结果,分支结构-“switch”结构,AREA Jump,CODE,READONLY num EQU 2;定义跳转表大小 ENTRYstart MOV r0,#1;设置3个参数 MOV r1,#3 MOV r2,#2arithfu;运算 CMP r0,#num;判断R0中的参数是否越界integer BHI Outofrange;参数超出跳转表范围直接赋值R0=0 xFF ADR r3,JumpTable;读跳转表首地址 LDR pc,r3,r0,LSL#2;查跳转表,确定跳转地址JumpTable DCD DoAdd DCD DoSub1 DCD DoSub2DoAdd DoSub1 DoSub2 Outofrange Save END,跳转表,跳转后执行,程序跳转示意图,LDR pc,r3,r0,LSL#2;查跳转表,确定跳转地址,例9.4在RVDS上的运行结果,循环结构,AREA Sort,CODE,READONLY ENTRYstart MOV r4,#0 LDR r6,=src;设置R6保存待排序数组首地址 ADD r6,r6,#len;让R6保存数组中最后一个地址outer;外循环起始 LDR r1,=srcinner;内循环起始 LDR r2,r1CMP r1,r6 BLT inner;内循环结束 ADD r4,r4,#4BLE outer;外循环结束 AREA Array,DATA,READWRITEsrc DCD 2,4,10,8,14,1,20;初始化待排序数组len EQU 7*4;初始化数组长度 END,内循环,外循环,例9.5在RVDS上的运行结果,子程序调用与返回,N EQU 100;定义N的值100AREA Examples,CODE,READONLY;声明代码段Examples3 ENTRY;标识程序入口 CODE32ARM_CODE LDR SP,=0X30003F00;设置堆栈指针 ADR R0,THUMB_CODE+1;BX R0;跳转并切换处理器状态 LTORG;声明文字池 CODE16THUMB_CODE LDR R0,=N;设置子程序SUM_N的入口参数 BL SUM_N;调用子程序SUM_N B THUMB_CODE SUM_N PUSH R1-R7,LR;寄存器入栈保护 MOVS R2,R0;将N的值复制到R2,并影响相应条件标志SUN_L1 ADD R0,R1 BHS SUM_END B SUN_L1SUM_ERR MOV R0,#0SUM_END MOV R8,R0;将结果保存在R8中 POP R1-R7,PC;寄存器出栈,返回END,初始化设置,切换工作状态,调用子程序,保存现场和断点,计算1到N之和,恢复现场和断点,例9.6在RVDS上的运行结果,ARM汇编语言与C/C+的混合编程,在嵌入式软件开发过程中,通常会使用包括ARM汇编语言和C/C+语言在内的多种语言。一般情况下,一个ARM工程(project)应该由多个文件组成,其中包括:扩展名为.s的汇编语言源文件扩展名为.c的C语言源文件扩展名为.cpp的C+源文件以及扩展名为.h的头文件等,ARM汇编语言与C/C+的混合编程,1初始化程序部分 硬件系统的初始化,包括设定CPU工作状态,中断使能,主频设定,以及RAM的控制参数设置及初始化等,通常都使用汇编代码。2初始化部分与主应用程序部分的衔接当所有的系统初始化工作完成之后,就需要把程序流程转入到应用程序。最简单的方法是,在汇编语言程序末尾使用跳转指令B或BL直接从启动代码转移到C/C+程序入口。,3.主应用程序的混合编程方式 汇编程序和C/C+程序之间的相互调用(ATPCS)在C/C+代码中嵌入汇编指令,51/52,基于ARM/Thumb指令集过程调用的规则ATPCS,PCS用于保证使用不同编程语言的子程序可以分开编写、编译,并成功连接,所以它实际上定义了一套有关过程(函数)调用者与被调用者之间的协议。PCS的制订是一系列指标的折衷(tradeoff),如生成代码的大小,调试功能的支持,函数调用上下文处理速度以及内存消耗。ARM基本的ATPCS规定了寄存器使用、数据栈使用以及参数传递这三方面的基本规则;而派生的其他几种特定的ATPCS则是在此基础上再添加其他规则(如支持子程序可重入性、数据栈界限检查等)而形成的。在基于ARM的混合编程技术中,C语言子程序只需开发者指定ATPCS类型,而汇编子程序则需完全依靠开发者来保证。,基本ATPCS(1):寄存器使用规则,寄存器R0R3(A1A4)用做子程序参数传递。被调用的子程序在返回前无需恢复其内容。寄存器R4R11(V1V8)用做子程序内的局部变量保存。如有使用则应进行保护和恢复。(Thumb程序通常只能使用R4R7)寄存器R12(IP)用作临时(scratch)指针。寄存器R13用作数据栈指针SP,不能用于其他用途。SP在子程序进入和退出时的值必须相等。寄存器R14用作连接寄存器LR。如果子程序中保存了返回地址,则R14可用于其他用途。寄存器R15是程序计数器PC,不能用于其他用途。,ATPCS中的寄存器使用规则,基本ATPCS(2):数据栈使用规则,ARM的数据栈可为FD(Full Descending),ED(Empty Descending),FA(Full Ascending)或EA(Empty Ascending),但ATPCS规定数据栈为FD类型,且8字节对齐的。数据栈指针(stack pointer)指向最后入栈的数据单元地址。数据栈基址(stack base)指向数据栈的最高地址。数据栈界限(stack limit)指向数据栈的最低地址。已占用的数据栈(used stack)指栈基址和SP之间的区域,其中包括栈指针对应的内存单元。数据栈中的数据帧(stack frame)指栈中为子程序分配的用来保存寄存器和局部变量的区域。,对于汇编代码来说,必须保证在进入该汇编代码后,直到调用外部代码之间,栈指针变化为偶数个字;应使用PRESERVE8伪指令告诉连接器,本汇编代码是8字节对齐的;应使用FRAME伪指令描述数据帧;,基本ATPCS(3):参数传递规则,1、子程序入口参数传递规则参数不超过4个时使用寄存器R0R3,依次将各字数据传送到寄存器;参数超过4个时将剩余的字数据传送到数据栈,入栈的顺序与参数顺序相反,即最后一个参数先入栈。2、子程序结果返回规则当结果为一个32位的整数时通过寄存器R0返回子程序结果;当结果为一个64位的整数时通过R0和R1返回子程序结果,以此类推;当对于位数更多的子程序结果,需要通过调用内存来传递。,56/52,C程序调用汇编函数,汇编程序的设置要遵循ATPCS 规则,保证程序调用时参数、寄存器和堆栈的正确使用。在汇编程序中使用EXPORT 伪指令声明本子程序,使其它程序可以调用此子程序。在C 语言程序中使用extern关键字声明要调用的汇编子程序为外部函数。,汇编程序调用C函数汇编程序的设置要遵循ATPCS 规则,保证程序调用时参数、寄存器和堆栈的正确使用。在汇编程序中使用IMPORT 伪指令声明将要调用的C 程序函数。在正确设置入口参数后使用BL 调用C程序函数。,AREA SCopy,CODE,READONLYEXPORT strcopy;声明strcopy为导出符号strcopy LDRB R2,R1,#1;R1中的值为源数据块的首地址 STRB R2,R0,#1;R0中的值为目标数据块的首地址 CMP R2,#0 BNE strcopy;未复制完,循环继续复制 MOV PC,LR;复制完毕,返回END,extern void strcopy(char*d,const char*s)/参数由左向右依次传递给R0R3:d为目标指针(R0);s为源指针(R1)int main(void)const char*srcstr=First string-soure;char deststr=Second string-destination;strcopy(deststr,srcstr);/调用汇编函数strcopy,数据块复制,*,58/52,例9.7在RVDS上的运行结果,int g(int a,int b,int c,int d,int e)return a+b+c+d+e,在汇编函数f中调用C函数g(),以实现下面的功能:int f(int i)return-g(i,2*i,3*i,4*i,5*i)EXPORT f AREA f,CODE,READONLYENTRYIMPORT g;声明g为外部引用符号 STR LR,SP,#-4;将断点存入堆栈 MOV R0,#2;i=2 ADD R1,R0,R0;(R1)=i*2 ADD R2,R1,R0;(R2)=i*3 ADD R3,R1,R2;(R3)=i*5 STR R3,SP,#-4;将(R3)即第5个参数i*5存入堆栈 ADD R3,R1,R1;(R3)=i*4 BL g;调用C函数g(),返回值在R0中 ADD SP,SP,#4;调整数据栈指针,准备返回 LDR PC,SP,#4;恢复断点END,*,60/52,例9.8在RVDS上的运行结果,C程序中嵌入汇编代码,ARM体系结构支持C、C+以及汇编语言的混合使用,内嵌汇编器还允许在C程序中嵌入汇编代码,以提高程序的效率。在ARM C语言程序中使用关键词_asm来标识一段汇编指令程序,其格式如下:_asm instruction;instruction instruction 如果一行有多个汇编指令,则指令之间用分号隔开;如果一条指令占多行,则要使用续行符号();在汇编指令段中可以使用C语言的注释语句。,内嵌汇编中使用物理寄存器的注意事项1,一般不要直接指定物理寄存器存放数据,而应该使用C变量,让编译器自动分配寄存器。,_asm/*错误 MOV R0,xADD y,R0,x/y,int cvar;_asm/*正确 MOV cvar,x ADD y,cvar,x/y,C编译器计算x/y值时会破坏R2,R3,R12和LR的值;更新N,Z,C和V条件标志位;并在R0中返回商,R1中返回余数。因此左边代码段中R0存放的数据在执行ADD指令前已被修改。建议也不要使用复杂的C表达式,因为编译器在计算汇编代码中的C表达式时,可能会使用物理寄存器(如R0R3、R12、SP、LR),并修改CPSR中的标志位。如果编译器无法分配使用合适的寄存器,将会报告寄存器冲突错误。,不要使用物理寄存器代替变量。尽管有时寄存器明显对应某个变量,但也不能直接使用寄存器代替变量。,64/52,内嵌汇编中使用物理寄存器的注意事项2,int example1(int x)/*错误 _asm ADD R0,R0,#1 return x;,int example1(int x)/*正确 _asm ADD x,x,#1 return x;,进入子程序example1后,参数x的值的确保存在寄存器R0中,但编译器认为内嵌汇编发生了寄存器冲突,可能使用其他寄存器存放x。因此左边代码段并不能完成对x的加1操作。,内嵌汇编语言中其他的注意事项,内嵌汇编指令可以使用C表达式,编译器会计算表达式的值并为其分配寄存器。但其值被视为无符号数;若为带符号数则用户需要自己处理与符号有关的操作;若内嵌汇编指令中的C表达式包含有逗号,则该表达式应该包含在括号中;如:_asmADD x,y,(f(),z);(f(),z)为C表达式内嵌汇编指令中常量前面的“#”可以省略,并使用“0 x”代替“&”表示十六进制数;不支持内存分配伪指令,可通过定义变量由编译器自动完成分配;不支持BX指令,并且只有B指令可以使用C程序标号,而BL不行;不能向PC赋值,程序跳转只能使用B或BL指令;内嵌的SWI和BL指令除了正常的操作数外,可增加几个可选的寄存器列表用于参数传送;,内嵌汇编指令的应用举例:两个数相加,#include int add(int i,int j)int res;/*定义中间变量res*/_asmADD res,i,j/;实现res=i+jreturn res;void main()int a;a=add(2,3);printf(addition result is:%dn,a);,求和子程序,调用子程序(注意书上漏印),例9.10所示程序在RVDS上的运行结果,作业,必做:、9.8、选做:9.6、9.7、9.12、9.14注意,除题目明确要求外,所有的程序均采用汇编语言编写,第9章 结束,