一、指令基本格式
{}{S} ,{,}
其中,<>内的项是必须的,{}内的项是可选的,如是指令助记符,是必须的,
而{}为指令执行条件,是可选的,如果不写则使用默认条件AL(无条件执行)。
Opcode 指令助记符,如LDR,STR 等
Cond 执行条件,如EQ,NE 等
S 是否影响CPSR 寄存器的值,书写时影响CPSR,否则不影响
Rd 目标寄存器
Rn 第一个操作数的寄存器
shifter_operand 第二个操作数
NZCV
条件码 助记符后缀 标志 含义
0000 EQ Z置位 相等
0001 NE Z清零 不相等
0010 CS C置位 无符号数大于或等于
0011 CC C清零 无符号数小于
0100 MI N置位 负数
0101 PL N清零 正数或零
0110 VS V置位 溢出
0111 VC V清零 未溢出
1000 HI C置位Z清零 无符号数大于
1001 LS C清零Z置位 无符号数小于或等于
1010 GE N等于V 带符号数大于或等于
1011 LT N不等于V 带符号数小于
1100 GT Z清零且(N等于V) 带符号数大于
1101 LE Z置位或(N不等于V) 带符号数小于或等于
1110 AL 忽略 无条件执行
几个特殊的名字 r13--sp r14--lr r15--pc
二、数据传送指令
2.1 LDR/STR 字传送32位
LDR r1, [r0, #0x12] ;将r0+12地址处的数据读出,保存到 r1中(r0的值不变)
LDR r1, [r0] ;将r0地址处的数据读出,保存到 r1中(零偏移)
LDR r1, [r0, r2] ;将r0+r2地址的数据读出,保存到 r1中(r0的值不变)
LDR r1, [r0, r2, LSL #2] ;将r0+r2×4 地址处的数据读出,保存到 r1中(r0,r2的值不变)
LDR Rd, label ;label为程序标号,label必须是当前指令的±4KB 范围内
LDR Rd, [Rn], #0x04 ;Rn 的值用作传输数据的存储地址。在数据传送后,将偏移量 0x04 与
Rn相加,结果写回到Rn中。Rn不允许是r15
2.2 类似的LDRB/STRB字节传送8位 LDRH/STRH半字传送16位,用户模式下的LDRT/STRT LDRBT/STRBT
LDRSB/STRSB有符号字节传送8位 LDRSH/STRSH有符号半字传送8位
2.3 LDM/STM
LDM/STM 的主要用途有现场保护、数据复制和参数传递等。其模式有8种,如下所示。前面4种用于数据块的传输,后面4种是堆栈操作。
Load from memory into register
Store from a register into memory
IA:每次传送后地址加4
IB:每次传送前地址加4
DA:每次传送后地址减4
DB:每次传送前地址减4
FD:满递减堆栈(常用)
ED:空递减堆栈
FA:满递增堆栈
EA:空递增堆栈
LDMIA r0!,{r3~r9} ;加载r0指向的地址上的多字数据,保存到 r3~r9中,r0值更新
STMIA r1!,{r3~r9} ;将r3~r9的数据存储到r1指向的地址上,r1值更新
使用 LDM/STM 进行数据复制
LDR r0, =SrcData ; 设置源数据地址
LDR r1, =DstData ; 设置目标地址
LDMIA r0, {r2~r9} ;加载8个字数据到寄存器r2~r9
STMIA r1, {r2~r9} ;存储寄存器r2~r9到目标地址
使用 LDM/STM 进行现场寄存器保护,常在子程序或异常处理使用。
SENDBYTE
STMFD SP!,{r0~r7,LR} ;寄存器压栈保护
…….
BL DELAY ;调用DELAY子程序
…….
LDMFD SP!,{r0~r7,PC} ;恢复寄存器,并返回
2.4 SPW&SWPB
SWP r1, r1, [r0] ;将r1的内容与r0指向的存储单元内容进行交换
SWPB r1, r2, [r0] ;将r0指向的存储单元内容读取一字节数据到r1中(高24位清零),并将r2的内容写入到该内存单元中(最低字节有效)
2.5 MRS/MSR
MRS r1, CPSR ;将CPSR 状态寄存器读取,保存到r1中
MRS r2, SPSR ;将SPSR 状态寄存器读取,保存到r1中
在ARM处理器中,只有 MSR指令可以直接设置状态寄存器 CPSR 或SPSR。状态寄存器的 32 位可以分为 4个8 位的域(field )。
bits[31 :24]为条件标志位域,用 f 表示;
bits[23 :16]为状态位域,用 s 表示;
bits[15 :8] 为扩展位域,用 x 表示;
bits[7:0] 为控制位域,用 c 表示;
MSR CPSR_c,#0xD3 ;CPSR[7:0]=0xD3 ,切换到管理模式
MSR CPSR_cxsf,r3 ;CPSR=R3
只有在特权模式下才能修改状态寄存器。
三、数据处理指令
感觉这华清写的总是多少有些问题,不是全相信,这里就先不笔记了,只是过目了不少指令,以后用到了网上查
四、乘法指令
4.1 下面指令完成R1=R2 ×R3+10 的操作。
MOV R0, #0x0A ;
MLA R1, R2, R3, R0 ; 乘-累加指令
4.2 下面指令完成(R1,R0)=R5 ×R8操作。
UMULL R0, R1, R5, R8; 无符号长乘指令 R0高32位 R1低32位
4.3下面的指令完成(R1,R0)= R5 ×R8+(R1,R0)操作。
UMLAL R0, R1, R5,R8; 无符号长乘-累加指令
4.4 SMULL SMLAL 符号的
五、跳转指令
5.1 除了B、BL、BX、BLX,另一种实现指令跳转的方式是通过直接向PC寄存器中写入目标地址值,实现在4GB地址空间中任意跳转,
这种跳转指令又被称为长跳转。
5.2 BL指令用于实现子程序调用。子程序的返回可以通过将 LR寄存器的值复制到PC寄存器来实现。下面三种指令可以实现子程序返回。
BX r14 (如果体系结构支持BX指令)。
MOV PC ,r14
当子程序在入口处使用了压栈指令:
STMFD r13 !, {,r14}
子程序结束后,可以使用出栈指令。
LDMFD r13 !, {,PC}
将子程序返回地址放入PC中。
5.3 BL&BLX完成状态切换式的跳转与返回
CODE32 ;ARM 代码
……
BLX TSUB ;调用Thumb子程序
……
CODE16 ;Thumb代码开始
TSUB
……
BX r14 ;返回ARM 状态
五、ARM汇编程序设计
5.1 ARM汇编器所支持的伪操作
5.1.1 符号定义伪操作
1. 全局变量定义伪操作
GBLA 伪操作用于定义一个全局的数字变量并初始化为0。
GBLL 伪操作用于定义一个全局的逻辑变量并初始化为F(假)。
GBLS 伪操作用于定义一个全局的字符串变量并初始化为空。
例子:
1)使用伪操作声明全局变量
GBLA Test1 ; 定义一个全局的数字变量,变量名为 Test1
Test1 SETA 0xaa ; 将该变量赋值为 0xaa
GBLL Test2 ; 定义一个全局的逻辑变量,变量名为 Test2
Test2 SETL {TRUE} ; 将该变量赋值为真
GBLS Test3 ; 定义一个全局的字符串变量,变量名为 Test3
Test3 SETS "Testing" ; 将该变量赋值为“Testing”
2)声明变量objectsize并设置其值为 0xff ,为“SPACE”操作做准备
GBLA objectsize
Objectsize SETA oxff
SPACE objectsize
2.局部变量定义伪操作
LCLA 伪操作用于定义一个局部的数字变量并初始化为0。
LCLL 伪操作用于定义一个局部的逻辑变量并初始化为 F(假)。
LCLS 伪操作用于定义一个局部的字符串变量并初始化为空。
例子:
1)使用伪操作声明局部变量
LCLA Test4 ;声明一个局部的数字变量,变量名为 Test4
Test3 SETA 0xaa ;将该变量赋值为 0xaa
LCLL Test5 ;声明一个局部的逻辑变量,变量名为 Test5
Test4 SETL {TRUE} ;将该变量赋值为真
LCLS Test6 ;定义一个局部的字符串变量,变量名为 Test6
Test6 SETS "Testing" ;将该变量赋值为“Testing”
2)下面的例子定义一个宏,显示了局部变量的作用范围。
MACRO ;声明一个宏
$label message $a ;宏原型
LCLS err ;声明局部字符串变量
$label
INFO 0,"err":CC::STR:$a
MEND ;宏结束,局部变量不再起作用
3.变量赋值伪操作(SETA、SETL 和SETS见上)
4.通用寄存器列表定义伪操作RLIST
将寄存器列表名称定义为 RegList,可在ARM指令LDM/STM 中通过该名称访问寄存器列表。
RegList RLIST {R0-R5 ,R8,R10}
5.协处理器寄存器名称定义伪指令CN
将协处理器寄存器6 命名为 Power
Power CN 6
6.协处理器名称定义伪操作CP
将协处理器6 命名为Dmu
Dmu CP 6
7.VFP 寄存器名称定义伪操作DN/SN
将VFP 双精度寄存器6 定义为energy
energy DN 6
将VFP 单精度寄存器16 定义为mass
mass SN 16
8.浮点寄存器FN
为浮点寄存器6 指定名称为 Energy
Energy FN 6
5.1.2 数据伪操作
1.用于分配字节存储单元的伪操作DCB
{label} DCB expr{,expr} label程序标号
分配一片连续的字节存储单元并初始化为指定字符串
Str DCB "This is a test !"
与C中的字符串不同,ARM汇编中的字符串不以null结尾,下面指令以ARM汇编形成一个C语言风格的字符串
C_string DCB "C_string",0
2.用于分配半字存储单元的伪操作DCW(DCWU) U,不严格半字对齐
分配一片连续的半字存储单元并初始化
DataTest DCW 1, 2, 3
3.用于分配字存储单元的伪操作DCD(DCWU) U,不严格字对齐
分配一片连续的字存储单元并初始化
DataTest DCD 4, 5, 6
4.用于为单精度浮点数分配内存单元的伪操作DCFS(DCFSU) U,不严格字对齐
分配一片连续的字存储单元并初始化为指定的单精度浮点数
FDataTest DCFS 2E5, -5E-7
5.用于为双精度浮点数分配内存单元的伪操作DCFD(或DCFDU) U,不严格字对齐
分配一片连续的字存储单元并初始化为指定的双精度浮点数
FDataTest DCFD 2E115, -5E7
6.分配以8个字节为单位的连续存储区域的伪操作DCQ(或DCQU) U,不严格字对齐
分配一片连续的存储单元并初始化为指定的值
DataTest DCQ 100
7.内存单元分配伪操作SPACE
分配连续100 字节的存储单元并初始化为 0
DataSpace SPACE 100
在Mydata段的开始可以 255 个初始化为 0 的字节单元
AREA Mydata,DATA,READWRITE
data1 SPACE 255;
8.定义结构化内存表首地址伪操作MAP
定义结构化内存表首地址的值为 0x100+R0
MAP 0x100,R0
9.定义结构化内存表中数据域的伪操作FILED
1)下面的例子定义了一个内存表,其首地址为固定地址 0x100,该结构化内存表包含 3 个域,A 的长度为
16个字节,位置为 0x100,B 的长度为 32 个字节,位置为 0x110,S 的长度为 256 个字节,位置为 0x130。
MAP 0x100 ; 定义结构化内存表首地址的值为 0x100。
A FIELD 16 ; 定义A 的长度为16字节,位置为0x100
B FIELD 32 ; 定义B 的长度为32字节,位置为0x110
S FIELD 256 ; 定义S 的长度为256 字节,位置为0x130
2)下面的例子显示了一个寄存器相关的首地址定义结构化内存表
MAP 0,r9 ; 将结构化内存表的首地址设为 r9的值
FIELD 4 ;
LAB FIELD 4 ;
LDR r0,LAB ;
最后的LDR 指令,相当于:
LDR r0,[r9,#4]
10.声明数据缓存池的伪操作LTORG
1)声明一个数据缓存池用来存储 0x12345678
LDR r0,=0x12345678;
ADD r1,r1,r0;
MOV PC,LR;
LTORG
……
2)在代码段中使用数据缓冲池
AREA Example,CODE,READONLY
Start BL func1;
func1 ;程序主体
;程序代码
LDR r1,=0x55555555
MOV PC,LR ;子程序结束
LTORG
Data SPACE 4200 ;清除4200 个内存字节
END
11.将内存单元的内容初始化为相对地址的伪操作DCDO
分配32 位的字单元,其值为 externsym基于r9 的偏移量
DCDO externsym
12.分配用于存放代码的内存单元伪指令DCI
DCI 伪操作和DCD伪操作非常相似,不同之处在于DCI 分配的内存中的数据被表示为指令,可用于通过宏操作来定义处理器不支持的指令。
DCI 伪操作要求内存对齐,对于ARM指令要求4 字节对齐,对于 Thumb 指令要求2 字节对齐。
下面的程序通过宏操作来定义处理器不支持的指令。
MACRO
Newinst $Rd,$Rm ;
DCI 0xe16f0f10:OR($Rd:SHL:12):OR:$Rm
MEND
13.用于分配由用户指定大小的内存单元的伪操作COMMON
下面的例子定义大小为255 字节的内存单元,该内存单元是字对齐的。
COMMON xyz,255,4
5.1.3 汇编控制伪操作 伪操作的嵌套不可超过256
1.IF、ELSE、ENDIF
IF {CONFIG}=16
BNE_rt_udiv_1 ;
LDR r0,=_rt_div0 ;
BX r0 ;
ELIF ...
...
ELSE
BEQ_rt_div() ;
ENDIF
2.WHILE、WEND
count SETA 1 ;
WHILE count<5 ;
count SETA count+1 ;
…
…
WEND
3.MACRO、MEND
下面的程序显示了一个完整的宏定义和调用过程。
;宏定义
MACRO ; 开始宏定义
$label mymacro $p1,$p2
;代码段1
$label.loop1 ;代码段2
;代码段3
BGE $label.loop1
$label.loop2 ;代码段4
BL $p1
BGT $label.loop2
;代码段5
ADR $p2
;代码段6
MEND
;程序汇编后,宏展开结果
abc mymacro subr1,de ;使用宏
;代码段1
abcloop1 ;代码段2
;代码段3
BGE abcloop1
abcloop2 ;代码段4
BL subr1
BGT abcloop2
;代码段5
ADR de
;代码段6
4.MEXIT
MACRO
$abc macro abc $paraml,$param2
;code
WHILE condition1
;code
IF condition2
;代码段
MEXIT
ELSE
;代码段
ENDIF
WEND
;代段段
MEND
5.1.4 信息报告伪操作
1.断言错误伪操作ASSERT
ASSERT 为断言错误伪操作。在汇编器对汇编程序进行第二遍扫描时,如果发现 ASSERT 条件不成立,汇编器将报告错误信息。
1)下面的程序在 Top 和Temp相等时报告错误
ASSERT Top<>Temp
2)当label1 代表的地址大于label2 所代表的地址时报告错误
ASSERT label1<=label2
2.诊断信息显示伪操作INFO 或“!”
INFO numeric-expression,string-expression
数字表达式,在汇编时计算。如果 numeric-expression 的值为0,则通过第一遍汇编并在第二遍汇编时报告“string-expression ”的内容;
如果 numeric-expression 的值不等于0 ,则在第一遍汇编过程中报告“string-expression ”的内容并中止汇编。
下面的程序在第二遍汇编扫描时报告版本信息,并判断cont1 和cont2 的关系。
INFO 0,”verion 1.0” ;在第二遍扫描时,报告版本信息
IF cont1>cont2 ;如果cont1>cont2
INFO 1,”cont1>cont2” ;则在第一遍扫描时报告“cont1>cont2 ”
ENDIF
3.设置列表选项伪操作OPT(略,见资料)
4.插入标题伪操作TTL和SUBT(略,见资料)
5.1.5 指令集选择伪操作
1.ARM和CODE32(意义相同)
AREA Init ,CODE,READONLY
……
CODE32 ; 通知编译器其后的指令为 32位的ARM 指令
LDR R0 ,=NEXT+1 ; 将跳转地址放入寄存器 R0
BX R0 ; 程序跳转到新的位置执行,并将处理器切换到 Thumb工作状态
……
CODE16 ; 通知编译器其后的指令为 16位的Thumb指令
NEXT LDR R3 ,=0x3FF
……
END ; 程序结束
2.THUMB
AREA ChangeState,CODE,READONLY
ARM
;下面的指令在ARM 状态下开始执行
LDR r0,=start+1 ;取出跳转地址,设置状态标志位
BX r0 ;跳转并切换程序状态
THUMB ;下面的指令序列为 Thumb-2指令
Gsthing PROC ;
B {pc}+2 ;#0x8002
B {pc}+4 ;#0x8004
3.CODE16见上
5.1.6 杂项伪操作
1.ALIGN
2.AREA
定义了一个代码段,段名为Init ,属性为只读。
AREA Init, CODE, READONLY
ENTRY ; 指定应用程序的入口点
...
END
3.END
4.ENTRY
5.EQU
Test EQU 50 ;定义标号Test 的值为50
Addr EQU 0x55,CODE32 ;定义Addr 的值为0x55,且该处为32位的ARM 指令。
6.EXPORT(或GLOBAL)
AREA Init,CODE ,READONLY
EXPORT Stest ; 声明一个可全局引用的标号Stest
……
END
7.EXPORTAS
AREA data1, DATA ;定义新的数据段 data1
AREA data2, DATA ;定义新的数据段 data2
EXPORTAS data2, data1 ;data2 中定义的符号将会出现在data1的符号表中
one EQU 2
EXPORTAS one, two
EXPORT one ; 符号two 将在目标文件中以“2”的形式出现
8.EXTERN
通知编译器当前文件要引用标号 Main,但Main在其他源文件中。
AREA Init ,CODE,READONLY
EXTERN Main ; 通知编译器当前文件要引用标号 Main,但Main 在其他源文件中定义
……
END
9.GET(或INCLUDE)
格式 GET filename
AREA Init ,CODE,READONLY
GET a1.s ;通知编译器当前源文件包含 a1.s
GET C:\a2.s ;通知编译器当前源文件包含 C:\ a2.s
……
END
10.IMPORT
IMPORT 和EXTERN 用法相似
11.INCBIN
使用INCBIN 可以包含任何格式的文件
AREA Init ,CODE,READONLY
INCBIN a1.dat ; 通知编译器当前源文件包含文件 a1.dat
INCBIN C:\a2.txt ; 通知编译器当前源文件包含文件 C:\a2.txt
……
END
12.KEEP
label ADC r2,r3,r4
KEEP label ;指示汇编器将label包含进符号表
ADD r2,r2,r5
13.NOFP
NOFP 伪操作用于告诉汇编器当前源文件中不包含浮点运算指令。
14.REQUIRE
REQUIRE伪操作用于定义段之间的相互依赖关系。
15.REQUIRE8 (或PRESERVE8)
REQUIRE8 伪操作指定当前文件堆栈要求8 字节对齐。它将传递 REQ8 连接选项到 ARM连接器。
PRESERVE8 伪操作指定当前文件堆栈要求8 字节对齐。它将传递 RPES8 连接选项到 ARM连接器。
连接器保证要求8 字节堆栈对齐的代码相互调用。
语法格式如下。
REQUIRE8 {bool}
PRESERVE8 {bool}
其中bool 的取值为{TRUE} 或{FALSE}
16.RN
regname RN 11 ; 将寄存器11命名为regname
sqr4 RN r6 ; 将寄存器6 定义为spr4
17.ROUT
ROUT 伪操作用于给一个局部变量定义作用范围。在程序中未使用该伪指令时,局部变量的作用范围为所
在的AREA,而使用ROUT 后,局部变量的作为范围为当前ROUT 和下一个ROUT 之间。
; code
routineA ROUT ;定义局部标号的有效范围,范围名称为 routineA
; code
3routineA ; code ;routineA 范围内的局部标号 3
; code
BEQ %4routineA ;若条件成立,跳转到 routineA 范围内的局部号4
; code
BGE %3 ;若条件成立,跳转到 routineA 范围内的局部标号3
; code
4routineA ; code ;范围A 内的局部标号4
; code
otherstuff ROUT ;定义新的局部标号有效范围
5.1.7 结构描述伪操作 感觉有点抽象,先不做笔记,记下一些指令名词
FRAME ADDRESS、FRAME POP、FRAME PUSH、FRAME REGISTER、FRAME RESTORE、FRAME RETURN ADDRESS、FRAME SAVE、
FRAME STATE REMEMBER、FRAME STATE RESTORE、FRAME UNWIND ON、FRAME UNWIND OFF、FUNCTION或PROC、ENDFUNC或ENDP
5.2 ARM汇编器所支持的伪指令 略
5.3 汇编语言文件格式
①符号名不应与指令或伪指令同名。如果要使用和指令或伪指令同名的符号要用双斜杠“|| ”将其括起
来,如“||ASSERT|| ”。
②; 直接的变量替换
GBLS add4ff
;
add4ff SETS "ADD r4,r4,#0xFF" ;给变量add4ff 赋值
$add4ff.00 ;引用变量
; codes
ADD r4,r4,#0xFF00
; 有特殊符号的变量替换
GBLS s1
GBLS s2
GBLS fixup
GBLA count
;
count SETA 14
s1 SETS "a$$b$count" ;s1 =a$b0000000E
s2 SETS "abc"
fixup SETS "|xy$s2.z|" ;fixup= |xyabcz|
|C$$code| MOV r4,#16 ;label= C$$code
③当在字符串中包含“$ ”或引号时,可以用“$$”表示“$ ”, 用两个双引号表示一个双引号。
④下面列出了被ARM汇编器预定义的寄存器名。列出目前常见的部分
r0 ~r15 和R0~R15(15个通用寄存器)。
a1~a4(参数、结果或临时寄存器,同 r0 ~r3 )。
v1~v8 (变量寄存器,同 r4 ~r11)。
sb 和SB(静态基址寄存器,同r9 )。
sl 和SL(栈顶指针寄存器,同 r10)。
fp 和FP(帧指针寄存器,同r11)。
ip 和IP (过程调用中间临时寄存器,同r12)。
sp 和SP(栈指针寄存器,同r13)。
lr 和LR(连接寄存器,同 r14)。
pc 和PC(程序计数器,同r15)。
cpsr 和CPSR(当前程序状态寄存器)。
spsr 和SPSR(保留程序状态寄存器)。
⑤汇编语言的程序结构
AREA Init ,CODE,READONLY
ENTRY
Start
LDR R0,=0x3FF5000
LDR R1,0xFF
STR R1,[R0]
LDR R0,=0x3FF5008
LDR R1,0x01
STR R1,[R0]
BL PRINT_TEXT ;子程序调用
……
PRINT_TEXT
……
MOV PC,BL ;子程序返回
……
END
5.4 ARM汇编编译器的使用
5.5 ARM汇编程序设计举例(参见pdf)
六、C、C++和汇编混合编程、
6.1 内联汇编和嵌入型汇编的使用
6.1.1 内联汇编
1.内联汇编支持大部分的ARM指令,但不支持带状态转移的跳转指令,如BX和BLX指令。其他一些注意事项可以见手册,或者未来遇到问题再记。
错误示例
int f(int x)
{
__asm
{
STMFD sp!, {r0} //保存r0不合法,因为在读之前没有对寄存器写操作
ADD r0, x, 1
EOR x, r0, x
LDMFD sp!, {r0} //不需要恢复寄存器
}
return x;
}
修改后,正确示例
int f(int x)
{
int r0;
__asm
{
ADD r0, x, 1
EOR x, r0, x
}
return x;
}
几个例子,见手册。
①字符串拷贝 ②中断使能 ③分隔符的计算
2.内联汇编中的限制 见手册
3.内联汇编中的虚拟寄存器
内联汇编指令中使用了寄存器r0,但对于C编译器,指令中出现的r0只是一个变量,并非实际的物理寄存器r0,当程序运行时,
可能是由物理寄存器r1来存放r0所代表的值。
4.内联汇编中的指令展开
5.内联汇编中的常数
如果在指令中使用了"#",则其后的表达式必为常数。
6.内联汇编指令对标志位的影响 需要多多注意
7.内联汇编指令中的操作数
8.函数调用和分支跳转
9.内嵌汇编中的标号
10.内嵌汇编器版本间的差异
不同版本的ARM编译器对内联汇编程序的语法要求有显著差异。在具体使用时请参见相关文档。
6.1.2 嵌入式汇编
1.嵌入式汇编语言语法 注意区分和内嵌汇编的区别
嵌入式汇编
#include
__asm void my_strcpy(const char *src, const char *dst) {
loop
LDRB r3, [r0], #1
STRB r3, [r1], #1
CMP r3, #0
BNE loop
MOV pc, lr
}
内嵌汇编
#include
void my_strcpy(const char *src, char *dst)
{
int ch;
__asm
{
loop:
LDRB ch, [src], #1
STRB ch, [dst], #1
CMP ch, #0
BNE loop
}
}
int main(void)
{
const char *a = "Hello world!";
char b[20];
my_strcpy (a, b);
printf("Original string: '%s'\n", a);
printf("Copied string: '%s'\n", b);
return 0;
}
2.嵌入式汇编语言的使用限制
3.嵌入式汇编程序表达式和C或C++表达式之间的差异
4.嵌入式汇编函数的生成
5.关键字_cpp
可用__cpp关键字从汇编代码中访问C或C++ 的编译时常量表达式,其中包括含有外部链接的数据或函数地址。
LDR r0, =__cpp(&some_variable)
LDR r1, =__cpp(some_function)
BL __cpp(some_function)
MOV r0, #__cpp(some_constant_expr)
6.手动重复解决方案
7.相关基类的关键字
8.成员类的关键字
9.调用非静态成员函数
10.嵌入式汇编版本间的差异
6.1.3 内联汇编中使用SP、LR和PC寄存器的遗留问题
使用RVCT v2.0 版本及其以后的编译器,要在C或C++代码中使用汇编访问SP、LR和PC寄存器可以使用下面几种方法。
①使用嵌入式汇编代码。嵌入式汇编支持所有的ARM指令,同时允许在代码中访问SP、LR和PC寄存器。
②在内联汇编中使用以下一些指令。
__current_pc():访问PC寄存器。
__current_sp():访问SP寄存器。
__return_address():访问LR,返回地址寄存器。
下面给出了两个访问SP、LR和PC寄存器的典型实例程序。
①使用编译器给定的指令。
void printReg()
{
unsigned int spReg, lrReg, pcReg;
__asm {
MOV spReg, __current_sp()
MOV pcReg, __current_pc()
MOV lrReg, __return_address()
}
printf("SP = 0x%X\n",spReg);
printf("PC = 0x%X\n",pcReg);
printf("LR = 0x%X\n",lrReg);
}
②使用嵌入式汇编
__asm void func()
{
MOV r0, lr
...
BX lr
}
6.1.4 内联汇编代码与嵌入式汇编代码之间的差异
6.2 从汇编代码访问C全局变量
对于无符号变量,使用:
LDRB/STRB:用于char 型;
LDRH/STRH:用于short 型(对于ARM体系结构 v3,使用两个 LDRB/STRB 指令);
LDR/STR:用于int 型。
下面的例子将整型全局变量globvar的地址载入r1、将该地址中包含的值载入r0、将它与2相加,然后将新值存回globvar中。
PRESERVE8
AREA globals,CODE,READONLY
EXPORT asmsubroutine
IMPORT globvar
asmsubroutine
LDR r1, =globvar ;read address of globvar into
;r1 from literal pool 从内存池中读取globvar变量的地址,加载到r1中
LDR r0, [r1]
ADD r0, r0, #2
STR r0, [r1]
MOV pc, lr
END
6.3 在C++中使用C头文件
要包括标准的系统 C 头文件,如 stdio.h,不必进行任何特殊操作。
要包含自己的C 头文件,用户必须将#include 命令包在 extern "C"语句中。
6.4 C、C++和ARM汇编语言之间的调用
6.4.1 相互调用的一般规则
6.4.2 C++的特定信息
6.4.3 混合编程调用举例
(1)从C调用汇编语言
下面的程序显示如何在C程序中调用汇编语言子程序,该段代码实现了将一个字符串复制到另一个字符串。
#include
extern void strcopy(char *d, const char *s);
int main()
{ const char *srcstr = "First string - source ";
char dststr[] = "Second string - destination ";
printf("Before copying:\n");
printf(" %s\n %s\n",srcstr,dststr);
strcopy(dststr,srcstr);
printf("After copying:\n");
printf(" %s\n %s\n",srcstr,dststr);
return (0);
}
下面为调用的汇编程序
PRESERVE8
AREA SCopy, CODE, READONLY
EXPORT strcopy
strcopy ;r0指向目的字符串
;r1指向源字符串
LDRB r2, [r1],#1 ;加载字节并更新源字符串指针地址
STRB r2, [r0],#1 ;存储字节并更新目的字符串指针地址
CMP r2, #0 ;判断是否为字符串结尾
BNE strcopy ;如果不是,程序跳转到 strcopy继续拷贝
MOV pc,lr ;程序返回
END
按以下步骤从命令行编译该示例:
① 输入armasm -g scopy.s 编译汇编语言源代码
② 输入armcc -c -g strtest.c 编译C源代码
③ 输入armlink strtest.o sc opy.o -o strtest 链接目标文件。
④ 将ELF/DWARF2兼容调试器与相应调试目标配合使用,运行映像。
(2)汇编语言调用C程序
下面的例子显示了如何从汇编语言调用C程序。
下面的子程序段定义了C语言函数。
int g(int a, int b, int c, int d, int e)
{
return a + b + c + d + e;
}
下面的程序段显示了汇编语言调用。假设程序进入f 时,r0中的值为i。
; int f(int i) { return g(i, 2*i, 3*i, 4*i, 5*i); }
PRESERVE8
EXPORT f
AREA f, CODE, READONLY
IMPORT g // 声明C 程序g()
STR lr, [sp, #-4]! // 保存返回地址 lr
ADD r1, r0, r0 // 计算2*i(第2 个参数)
ADD r2, r1, r0 // 计算3*i(第3 个参数)
ADD r3, r1, r2 // 计算5*i
STR r3, [sp, #-4]! // 第五个参数通过堆栈传递
ADD r3, r1, r1 // 计算4*i(第4 个参数)
BL g // 调用C 程序
ADD sp, sp, #4 // 从堆栈中删除第5 个参数
LDR pc, [sp], #4 // 返回
END
(3)从C++调用C
下面的例子显示了如何从C++程序中调用C函数。
下面的C++ 程序调用了C程序。
struct S { // 本结构没有基类和虚函数
S(int s):i(s) { }
int i;
};
extern "C" void cfunc(S *);
// 被调用的C 函数使用extern “C”声明
int f(){
S s(2); // 初始化 's'
cfunc(&s); // 调用C 函数 'cfunc' 将改变 's'
return si*3;
}
下面显示了被调用的C程序代码。
struct S {
int i;
};
void cfunc(struct S *p) {
p->i += 5;
}
(4)从C++中调用汇编
下面的例子显示了如何从C++中调用汇编程序。
下面的例子为调用汇编程序的C++代码。
struct S { // 本结果没有基类和虚拟函数
//
S(int s) : i(s) { }
int i;
};
extern "C" void asmfunc(S *); // 声明被调用的汇编函数
int f() {
S s(2); // 初始化结构体 's'
asmfunc(&s); // 调用汇编子程序 'asmfunc'
return s.i * 3;
}
下面是被调用的汇编程序。
PRESERVE8
AREA Asm, CODE
EXPORT asmfunc
asmfunc // 被调用的汇编程序定义
LDR r1, [r0]
ADD r1, r1, #5
STR r1, [r0]
MOV pc, lr
END
(5)从C中调用C++
下面的例子显示了如何从C++代码中调用C程序。
下面的代码显示了被调用C++代码。
struct S { // 本结构没有基类和虚拟函数
S(int s) : i(s) { }
int i;
};
extern "C" void cppfunc(S *p) {
// 定义被调用的C++代码
// 连接了C功能
p->i += 5; //
}
调用了C++代码的C函数。
struct S {
int i;
};
extern void cppfunc(struct S *p);
int f(void) {
struct S s;
s.i = 2;
cppfunc(&s);
return s.i * 3;
}
(6)从汇编中调用C++程序
下面的代码显示了如何从汇编中调用C++程序。
下面是被调用的C++程序。
struct S { // 本结构没有基类和虚拟函数
S(int s) : i(s) { }
int i;
};
extern "C" void cppfunc(S * p) {
// 定义被调用的C++ 功能
// 功能函数体
p->i += 5;
}
在汇编语言中,声明要调用的C++功能,使用带连接的跳转指令调用C++功能。
AREA Asm, CODE
IMPORT cppfunc ; 声明被调用的 C++ 函数名
EXPORT f
f
STMFD sp!,{lr}
MOV r0,#2
STR r0,[sp,#-4]! ; 初始化结构体
MOV r0,sp ; 调用参数为指向结构体的指针
BL cppfunc ; 调用C++ 功能'cppfunc'
LDR r0, [sp], #4
ADD r0, r0, r0,LSL #1
LDMFD sp!,{pc}
END
(7)在C和C++函数间传递参数
下面的例子显示了如何在C和C++函数间传递参数。
下面的代码为C++函数。
extern "C" int cfunc(const int&);
// 声明被调用的C 函数
extern "C" int cppfunc(const int& r) {
// 定义将被C 调用的C++ 函数
return 7 * r;
}
int f() {
int i = 3;
return cfunc(i); // 相C 函数传参
}
下面为C函数。
extern int cppfunc(const int*);
int cfunc(const int *p) {
int k = *p + 4;