1、内联汇编使用
内联汇编语法
内联汇编使用“_asm”(C++)和“asm”(C 和 C++)关键字声明,语法格式如下所示。
__asm("instruction[;instruction]"); // 必须为单条指令
__asm{instruction[;instruction]}
__asm{
...
instruction
...
}
asm("instruction[;instruction]"); // 必须为单条指令
asm{instruction[;instruction]}
asm{
...
instruction
...
}
内联汇编支持大部分的 ARM 指令,但不支持带状态转移的跳转指令,如 BX 和 BLX 指
令,详见 ARM 相关文档。
由于内联汇编嵌入在 C 或 C++程序中,所有在用法上有其自身的一些特点。
① 如果同一行中包含多条指令,则用分号隔开。
② 如果一条指令不能在一行中完成,使用反斜杠“/”将其连接。
③ 内联汇编中的注释语句可以使用 C 或 C++风格的。
④ 内联汇编语言中的寄存器名被编译器视为 C 或 C++语言中的变量,所以内联汇编中
出现的寄存器名不一定和同名的物理寄存器相对应。这些寄存器名在使用前必须声明,否则
编译器将提示警告信息。
⑤ 内联汇编中的寄存器(除程序状态寄存器 CPSR 和 SPSR 外)在读取前必须先赋值,
否则编译器将产生错误信息。
错误的内联汇编函数如下所示。
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;
}
内联汇编中的限制
可以在内联汇编代码中执行的操作有许多限制。这些限制提供安全的方法,并确保在汇
编代码中不违反 C 和 C++ 代码编译中的假设。
① 不能直接向程序计数器 PC 赋值。
② 内联汇编不支持标号变量。
③ 不能在程序中使用“.”或{PC}得到当前指令地址值。
④ 在 16 进制常量前加“0x”代替“&”。
⑤ 建议不要对堆栈进行操作。
⑥ 内嵌汇编不支持的指令有 BX、BLX、BXJ 和 BKPT 指令。而 LDM、STM、LDRD
和 STRD 指令可能被等效为 ARM LDR 或 STR 指令。
2、嵌入式汇编
嵌入式汇编语言语法
嵌入式汇编函数定义由 --asm(C 和 C++)或 asm(C++) 函数限定符标记,可用于:
• 成员函数;
• 非成员函数;
• 模板函数;
• 模板类成员函数。
用__asm 或 asm 声明的函数可以有调用参数和返回类型。它们从 C 和 C++中调用的方式
与普通 C 和 C++函数调用方式相同。嵌入式汇编函数语法是:
__asm return-type function-name(parameter-list)
{
// ARM/Thumb/Thumb-2 assembler code
instruction[;instruction]
...
[instruction]
}
嵌入式汇编的初始执行状态是在编译程序时由编译选项决定的。这些编译选项如下
所示:
• 如果初始状态为 ARM 状态,则内嵌汇编器使用–arm 选项;
• 如果初始状态为 Thumb 状态,则内嵌汇编器使用–thumb 选项。
内联汇编代码与嵌入式汇编代码之间的差异
本节总结了内联汇编和嵌入式汇编在编译方法上存在的差异:
• 内联汇编代码使用高级处理器抽象,并在代码生成过程中与 C 和 C++代码集成。因
此,编译程序将 C 和 C++代码与汇编代码一起进行优化。
• 与内联汇编代码不同,嵌入式汇编代码从 C 和 C++代码中分离出来单独进行汇编,
产生与 C 和 C++源代码编译对象相结合的编译对象。
• 可通过编译程序来内联内联汇编代码,但无论是显式还是隐式,都无法内联嵌入式汇
编代码。
下表总结了内联汇编程序与嵌入式汇编程序之间的主要差异。
3、汇编代码访问 C 全局变量
在汇编代码中访问 C 全局变量,只能通过地址间接访问全局变量。要访问全局变量,必
须在汇编中使用 IMPORT 伪操作输入全局变量,然后将地址载入寄存器。可以根据变量的
类型使用载入和存储指令访问该变量。
对于无符号变量,使用:
• LDRB/STRB:用于 char 型;
• LDRH/STRH:用于 short 型(对于 ARM 体系结构 v3,使用两个 LDRB/STRB 指令);
• LDR/STR:用于 int 型。
对于有符号变量,请使用等效的有符号数的 Load/Store 指令,如 LDRSB 和 LDRSH。
下面的例子将整型全局变量 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
4、在 C++中使用 C 头文件
本节描述如何在 C++代码中使用 C 头文件。从 C++调用 C 头文件之前,C 头文件必须包
含在 extern "C"命令中。本节包含以下两部分内容:
• 在 C++中使用系统的 C 头文件;
• 在 C++中使用自定义的 C 头文件。
在 C++中使用系统 C 头文件
要包括标准的系统 C 头文件,如 stdio.h,不必进行任何特殊操作。例如:
#include <stdio.h>
int main()
{
... // C++ 代码
return 0;
}
C++中使用自定义的 C 头文件
要包含自己的 C 头文件,用户必须将#include 命令包在 extern "C"语句中。可以用以下方
法完成此操作:
• 在#include 文件之前使用 extern,如下例所示。
// C++ code
extern "C" {
#include "my-header1.h"
#include "my-header2.h"
}
int main()
{
// ...
return 0;
}
• 将 extern "C"语句添加到头文件,如下例所示。
/* C header file */
#ifdef __cplusplus /* Insert start of extern C construct */
extern "C" {
#endif
/* Body of header file */
#ifdef __cplusplus /* Insert end of extern C construct */
} /* The C header file can now be */
#endif /* included in either C or C++ code. */
5、C、C++ 和 ARM 汇编语言之间的调用
本节通过例子展示C、C++和ARM汇编语言之间的调用。
(1) 从 C 调用汇编语言
下面的程序显示如何在 C 程序中调用汇编语言子程序,该段代码实现了将一个字符串复
制到另一个字符串。
#include <stdio.h>
extern void strcopy(char *d, const char *s);
int main()
{
const char *srcstr = "First string - source ";
char dststr[] = "Second string - destination ";
/* 下面将 dststr 作为数组进行操作 */
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 scopy.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) {
/*定义被调用的 C 功能 */
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);
/* 声明将会被调用的 C++功能 */
int f(void) {
struct S s;
s.i = 2; /* 初始化 S */
cppfunc(&s); /* 调用 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*);
/* 声明将被调用的 C++函数 */
int cfunc(const int *p) {
/*定义被 C++调用的 C 函数*/
int k = *p + 4;
return cppfunc(&k);
}
(8)从 C 或汇编语言调用 C++
下面的例子综合显示了如何从 C 或汇编语言中调用非静态、非虚的 C++成员函数。可以
使用编译器编译出的汇编程序查找已延伸的函数名。
下面是被调用的 C++成员函数。
struct T {
T(int i) : t(i) { }
int t;
int f(int i);
};
int T::f(int i) { return i + t; }
// 定义将被 C 调用的 C++功能函数
extern "C" int cfunc(T*);
// 声明将被 C++调用的 C 函数
int f() {
T t(5); // create an object of type T
return cfunc(&t);
}
下面为调用 C++的 C 语言函数。
struct T;
extern int _ZN1T1fEi(struct T*, int);
/* 被调用的 C++函数名 */
int cfunc(struct T* t) {
/* 定义被 C++调用的 C 函数 */
return 3 * _ZN1T1fEi(t, 2); /* 实现 3 乘以 t->f(2)功能 */
}
下面为调用 C++的汇编函数。
EXPORT cfunc
AREA foo, CODE
IMPORT _ZN1T1fEi
cfunc
STMFD sp!,{lr} ;此时 r0 已经包含了指向对象的指针
MOV r1, #2
BL _ZN1T1fEi
ADD r0, r0, r0, LSL #1 ;r0 乘以 3
LDMFD sp!,{pc}
END
下面的例子显示了如何用嵌入式汇编语言实现上面的例子。在此例中,使用 __cpp 关键
字引用该函数。因此,用户不必了解已延伸的函数名。
struct T {
T(int i) : t(i) { }
int t;
int f(int i);
};
int T::f(int i) { return i + t; }
// 定义被 C++调用的汇编功能
__asm int asm_func(T*) {
STMFD sp!, {lr}
MOV r1, #2;
BL __cpp(T::f);
ADD r0, r0, r0, LSL #1 ;r0 乘以 3
LDMFD sp!, {pc}
}
int f() {
T t(5); // 创建 T 类型的对象
return asm_func(&t);
}