ARM体系结构(7)-C语言和汇编混合编程

【1】为什么要混合编程?

  1. 如果只用汇编,可以实现所有功能,但是工作量太大
  2. 只要C语言,完成不了所有的工作,有些情况必须使用汇编。 启动代码、中断异常例程实现、开关中断
  3. 如果想让代码效率高一些,需要使用汇编。

【2】混合编程有哪几种情况?

  1. 汇编程序中, 访问C语言程序中的变量(全局变量)
  2. C语言程序中, 访问汇编程序中的数据
  3. 汇编程序中, 调用C语言程序中的函数
  4. C语言程序中, 调用汇编程序中的子程序

5. C函数中,嵌入汇编程序

【3】汇编程序中, 访问C语言程序中的变量(全局变量)

C语言部分:

  int test = 6;
    // test:
    // .word 6

汇编部分:

ldr r0,=test @访问
ldr r1,[r0]  @读
mov r2,#8
str r2,[r0]  @写
ldr r1,[r0]  @读

【4】汇编程序中, 如何调用C语言程序中的函数?

比如在test.c文件中定义一个c语言函数:

int mymax(int x,int y)
{
return x > y ? x:y;
}

如果在c语言中直接调用函数我们知道如下使用:

mymax(1,2);

那么汇编中如何调用c函数mymax,这个过程设计到以下问题:

  1. 汇编如何传参给c函数mymax

形参存储在哪里?

  1. 形参的前4个参数存储在R0-R3

从第5个参数开始存储在栈 实参传给形参: @参数1赋值1 mov r0,#1 @参数2赋值2 mov r1,#2 2.如何调用到mymax bl mymax @跳转到C语言mymax执行,保存pc到lr

  1. c函数返回值如何给汇编

返回值存放在哪里?

  1. 返回32bit的整数时, 返回值存放在R0中
  2. 返回64bit的整数时, 返回值存放在R0和R1中
mov r4,r0  @将mymax返回值保存到r4

整个汇编代码如下:

@参数1赋值1
mov r0,#1
@参数2赋值2
mov r1,#2
@跳转到C语言mymax执行,保存lr
bl mymax
@返回值自动在r0,将mymax返回值保存到r4
mov r4,r0

【5】c语言中如何访问汇编中的数据

汇编中如何实现变量:

@int var = 0x12345678;
.global var
var:
.word 0x12345678

C语言使用:

int tmp=0;
extern int var;
//读
tmp = var;
//写
var = 1;

汇编中验证c语言中var被改成1是否成功;

ldr r5, var

【6】C语言如何调用汇编子程序

汇编中实现一个myadd子程序,实现两数的相加

/*
int myadd(int x,int y)
{
return x+y;
}
*/
.global myadd
myadd:
add r0,r0,r1  @r0,r1保存形参1和形参2,函数返回结果保存到r0
mov pc,lr     @跳回调用前位置

C语言中如何调用:

extern int myadd(int x,int y);
int tmp=0;
tmp = myadd(1,2);   //形参1和2会被存到r0,r1 ,返回值存在r0,如果大于32位,存在r0和r1

关于汇编子程序的实现,其实有一个标准。

为了让不同的编译器编译的程序之间能够互相调用,为子程序间调用指定了一定的规则。

叫做AAPCS(Procedure Call Standard for Arm Architecture)

其中对于寄存器的使用规定如下:

  1. 子程序间通过寄存器r0~r3来传递参数,如果参数多余4个就要用堆栈来传递,被调用子程序在返回前要恢复寄存器r0~r3的值。
  2. 子程序使用寄存器r4~r11来保存局部变量。如果子程序使用到了r4~r11中的某些寄存器,子程序进入时必须保存这些寄存器的旧的值,在返回前要恢复这些寄存器的值。如果没有用到r4~r11的相关寄存器就不需要做这个。
  3. R13(sp)和r14(lr)还有r15(pc)不能用作他用。

从这个标准可以看到子程序形参最好不要超过4个,局部变量个数最好不要超过8个,否者影响效率。

采用AAPCS标准书写的汇编子程序样例代码:

/*
int mymaxx(int x,int y)
{
return x > y ? x:y;
}
*/
.global mymaxx
mymaxx:
push {r4}  @其中用到r4寄存器作为局部变量暂时保存最大值,将其push入栈内
cmp r0,r1
movge r4,r0
movlt r4,r1
mov r0,r4  
pop {r4}  @返回之前恢复r4
mov pc,lr

【7】 C函数中,如何嵌入汇编程序?

GNU 内联汇编语法:

1> 格式

 asm volatile (
 “asm code”
:output
:input
:changed
                   );

必须以“;”结尾,不管多长汇编代码对于C都只有一条语句。

asm 内嵌汇编关键字

volatile 告诉编译器不要优化内嵌汇编代码,如果想优化可以不加。

ANSI C规范下是__asm__ __volatile__ 前后两下划线

2> 汇编代码 (asm code)

asm code必须放到一个字符串内,但是字符串之间不能直接按回车换行。

可以写成多个字符串,只要字符串之间不加任何符号。

例子:

"mov r0,r0\n"        //指令之间必须要换行
"mov r1,r1\n"
"mov r2,r2\n"

3>输出部分 output

实现从asm ---> C变量。

       asm  volatile (
                    "asm code"
                    :“constraint”(variable)
            );
 
            constraint(约束)定义variable的存放位置:
                                r            使用任何可用的通用寄存器
                                m            使用变量的内存地址

            output修饰符:
                                +            可读可写
                                =            只写
                                &   该输出操作数不能使用输入部分使用过的寄存器,只能 +& 或 =& 方式使用

4> 输入部分 input

实现 C变量---> asm

     asm volatile (
                    "asm code"
                    :
                    :“constraint”(variable / immediate)
            );
 
            constraint定义variable / immediate的存放位置:
                                r            使用任何可用的通用寄存器(变量和立即数都可以)
                                m            使用变量的内存地址(不能用立即数)
                                i             使用立即数(不能用变量)

5>占位符

Myadd{
            int a = 100,b = 200;
            int result;
            asm volatile(
                    “mov    %0,%3\n”        //mov     r3,#123      %0代表result,%3代表123(编译器会自动加 # 号)
                    “ldr    r0,%1\n”        //ldr     r0,[fp, #-12]        %1代表 a 的地址
                    “ldr    r1,%2\n”        //ldr     r1,[fp, #-16]        %2代表 b 的地址
                    “str    r1,%1/n/t”        //str     r1,[fp, #-12]        如果用错指令编译时不会报错,要到汇编时才会
                    :“=r”(result),“+m”(a),“+m”(b)             out1是%0,out2是%1,...,outN是%N-1
                    :“i”(123)                                     in1是%N(接着out),in2是%N+1,...
            );
}

6> changed 告诉编译器你修改过的寄存器,编译器会自动把保存这些寄存器值的指令加在内嵌汇编之前,再把恢复寄存器值的指令加在内嵌汇编之后。

如果修改了没有在输入或输出中定义的任何内存位置,必须在changed列表里加上“memory”

     int add(unsigned int a, unsigned int b)
     {
          int sum = 0;     
          asm volatile (
              "add r4, %1, %2\n"
              "mov %0, r4\n"
              : "=r"(sum)
              : "r"(a), "r"(b)
              : "r4"        r4寄存器遭到了破坏,或者”memory”              
          );
          return sum;
     }

看看一个具体例子的实现步骤:

将c语言函数

int myadd(int x,int y)
{
int sum = 0;
sum = x+y; 
return sum;
}

用内联汇编实现。

先分析:

实现的加功能,不用说 add指令肯定用。Output是变量sum,input是x,y。

  1. 实现 asm code
int myadd(int x,int y)
{
int sum = 0;
/*sum = x+y; */
asm volatile(
"add sum,x,y\n"
:
:
:
);
return sum;
}

b.实现输出 output

int myadd(int x,int y)
{
int sum = 0;
/*sum = x+y; */
asm volatile(
"add r4,x,y\n"
"mov %0,r4\n"      //%0占位符表示output 0,其实就是 sum
:"=r"(sum)      //= 表示只写 r表示将sum变量存入寄存器
:
:
);
return sum;
}

C.实现输入部分 input

int myadd(int x,int y)
{
int sum = 0;
/*sum = x+y; */
asm volatile(
"add r4,%1,%2\n" //占位符%1,%2 表示 input1,input2,这里是x,y
"mov %0,r4\n"
:"=r"(sum)      //= 表示只写, r表示将sum变量存入寄存器
:"r"(x),"r"(y)
:
);
return sum;
}

d.实现 changed

int myadd(int x,int y)
{
int sum = 0;
/*sum = x+y; */
asm volatile(
"add r4,%1,%2\n" //占位符%1,%2 表示 input1,input2,这里是x,y
"mov %0,r4\n"
:"=r"(sum)      //= 表示只写, r表示将sum变量存入寄存器
:"r"(x),"r"(y)
:"r4" //这里我们改变了r4寄存器的值,在changed告诉编译器,让编译器自动为我们保存原值和恢复
);
return sum;
}
举报
评论 0