ARM架构之混合编程

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); 
}
  • 18
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值