GCC编译器支持直接在C或者C++代码中,嵌入ARM汇编代码。其基本格式非常简单,大致如下:

__asm__ [__volatile__] ( assembler template
: [output operand list] /* optional */
: [input operand list] /* optional */
: [clobbered register list] /* optional */
);

首先是关键字“__asm__”,其实也可以写成“asm”。但是“asm”并不是所有版本的GCC编译器都支持的,而且有可能和程序中别的地方定义的变量或函数名冲突,所以用“__asm__”的话,兼容性会好一点。

下面是“__volatile__”关键字,这个是可选的,其作用是禁止编译器对后面编写的汇编指令再进行优化。一般情况下,自己写的汇编代码肯定是自己进行设计优化过了的,如果编译器再进行优化的话,很有可能效果还不如不优化,而且也有可能会出现奇怪的错误,所以通常都会带上这个关键字。同样,“__volatile__”也可以写成“volatile”,但可能兼容性会没那么好。

下面,在括号里面的,就是真正的汇编代码了,其主要有四部分组成。第一个是具体的汇编代码,这是必需的。而后面三个是一些辅助参数,这些参数是可选的。

各个部分间使用冒号“:”进行分割。如果前面的部分没有使用,而后面的部分使用了,则前面的部分也需要用冒号留空。例如:

__asm__ __volatile__ ("msr cpsr, %0" : : "r" (status));

可以看出,本例中没有第二部分(输出参数列表),只有第三部分(输入参数列表),但它们中间任然要留出冒号进行分割。同时,也没有第四部分,但并不需要在第三部分后面加上冒号。

下面一一解释各个部分的作用:

1)汇编代码模板

所有的汇编代码必须用双引号括起来。如果有多行汇编代码的话,每一条语句都要用双引号括起来,并且在代码后面要加上换行符(“\n”或者“\n\t”)。这样做是因为GCC会将汇编代码部分作为字符串形式直接传给汇编器,加上换行符后,汇编器就能准确知道哪些字符串表示的是一条汇编语句。同时,为了增加可读性,每条汇编语句都可以换行。

其具体形式如下:

__asm__ __volatile__ ( "instruction 1\n\t"
"instruction 2\n\t"
......
"last instruction"
);

因为汇编代码部分是必需的,所以即使一行汇编代码也没有,也需要传入空字符串(""),否则会报错。

2)输出操作数列表和输入操作数列表

前面介绍了,第二部分和第三部分分别表示输出操作数列表和输入操作数列表。

输入操作数表示要作为汇编代码输入的C表达式,而输出操作数刚好相反,表示汇编代码处理完后要输出结果的C表达式。如果有多个输出或输入表达式,需要用逗号(“,”)将它们分隔开来。

可以再前面的汇编代码模板中直接应用定义的输出操作数和输入操作数,其用法是使用百分号(“%”)后面接一个数字,0表示定义的第一个操作数,1表示定义的第二个操作数,依次类推。下面举个例子:

__asm__("mov %0, %1, ror #1"
: "=r" (result)
: "r" (value)
);

这里%0代表后面定义的第一个操作数,即输出操作数,代表C语言中的result变量。%1代表定义的第二个操作数,即输入操作数,代表C语言中的value变量。其作用是将value的值右移一位,然后保存到result中。

每一个操作数由三部分组成,分别是修改符(Modifier),限定符(Constraint)和C表达式,其中修改符是可选的。具体形式如下:

"[modifier]constraint" (C expression)

修改符和限定符要用双引号括起来,而C表达式要用括号括起来。那么这些修改符和限定符又是什么呢?有什么作用呢?

我们接下来先来说说所谓的限定符。可以看出,操作数在这里的作用是将C语言定义的变量与汇编语言中要使用到的变量进行一一对应。但并不是所有的汇编指令都可以接受任何类型的变量作为输入或输出变量的,因此汇编器需要知道这些变量到底用在什么地方,从而帮助在传递之前做一些转换。常用的限定符主要有以下一些,而且汇编语句到底是ARM的还是Thumb的,对限定符的定义也会不同:

限定符 在ARM指令集下 在Thumb指令集下
f 浮点寄存器f0...f7 N/A
h N/A 寄存器r8...r15
G 浮点常量立即数 N/A
H 和G作用相同 N/A
I 数据处理指令中用到的立即数 范围为0...255的常量
J 范围为-4095...4095的索引常量 范围为-255...-1的常量
K 和I作用相同 和I作用相同
L 和I作用相同 范围为-7...7的常量
l 和r作用相同 寄存器r0...r7
M 范围为0...32或者是2的幂次方的常量 范围为0...1020的4的倍数的常量
m 内存地址 内存地址
N N/A 范围为0...31的常量
O N/A 范围为-508...508的4的倍数的常量
r 通用寄存器r0...r15 N/A
w 向量浮点寄存器s0...s31 N/A
X 任何类型的操作数 任何类型的操作数

看起来很复杂,但是常用的也就是r,f和m等几个。

好,说完了限定符,下面来看看修改符。修改符是加在限定符之前的,并且是可选的,如果没有修改符的话,则表明这个操作数是只读的。这个对输入操作数没有问题,但是对输出操作数来说,肯定是需要被修改的,那怎么办呢?答案就是使用修改符,修改这个操作数的属性。目前,GCC中定义了三个修改符,分别是:

修改符 含义
= 只写操作数,通常用于输出操作数中
+ 可读且可写操作数,必须要列在输出操作数中
& 寄存器只能用于输出

所以,作为输出操作数,只需要在限定符前加上“=”就可以了。

在汇编代码中,请绝对不要修改输入操作数的值。

如果想让一个C变量既作为输入操作数,也作为输出操作数的话,可以使用“+”限定符,并且这个操作数只需要在输出操作数列表中列出就行了。例如:

__asm__("mov %0, %0, ror #1"
: "+r" (y)
);

上面的例子是将变量y中的值又移1位。因为输入和输出操作数是一个,所以该操作数要既可读也可写,因此添加了“+”修改符。

但是GCC的老版本不一定支持“+”修改符,如果也想达到前面的这种输入和输出操作数是一个的目的,可以用一种变通的做法,并且这种变通的做法GCC的新版本也是支持的。其实,在限定符中,也可以使用数字,其作用是指代前面定义的操作数,0代表第一个,1代表第二个,以此类推。例如:

__asm__("mov %0, %0, ror #1"
: "=r" (y)
: "" (y)
);

如果GCC编译器支持的话,这个例子的效果和前面的例子是相同的。本例不同的是,先定义了一个可写的输出变量,同时在输入变量列表中,明确用数字0指出了前面定义的第一个操作数同时也要用来作为输入操作数。

好了,已经介绍了两个修改符的用处了,还剩下最后一个“&”。前面的例子是明确要求输出操作数和输入操作数使用同一个寄存器,但有时候刚好相反,输出操作数使用的寄存器一定不能和输入操作数使用的寄存器一样。但是,由于编译器的优化,是完全有可能出现同一个寄存器既用作输入操作数也用作输出操作数的情况的。这时,可以在输出操作数中使用“&”修改符,明确告诉编译器,代表输出操作数的寄存器一定不能使用输入操作数已经使用过的寄存器。下面举个例子:

__asm__ __volatile__("ldr %0, [%1]\n\t"
"str %2, [%1, #4]"
: "=&r" (rdv)
: "r" (&table), "r" (wdv)
: "memory"

本例中,将操作一个table数组,读出它的第一个数存放到rdv中,然后修改第二个数为wdv中存放的值。乍看一下没什么问题,但是如果编译器用同一个寄存器来表示输入操作数&table(%1)和输出操作数rdv(%0)怎么办呢?执行完第一条语句之后,table数组的地址就被修改掉了。所以,可以在输出操作数中加上一个“&”修改符,强制保证输出操作数不能和输入操作数复用同一个寄存器,这个问题就解决了。如果汇编代码中有输入寄存器还没有使用完毕,就对输出操作数进行修改的情况,则特别需要用“&”修改符,保证不复用。

3)修改寄存器列表
在汇编指令中,有可能会用到一些指定的寄存器,但是在执行你定义的汇编程序时,那个指定的寄存器有可能另有别的用途,存放了非常重要的数据。等你的程序执行完成后,那个寄存器的值已经被你修改过了,肯定会造成执行错误。因此,在执行你的程序之前必须要做必要的备份和恢复的动作。但是,编译器并不会分析你的汇编代码,找出这种被你修改过,需要恢复的寄存器,因此你必须显式的告诉编译器,被你修改过的寄存器有哪些。这就是修改寄存器列表所起到的作用。

对于嵌入内联ARM汇编来说,此列表中的值有下面三种类型:

类型 作用
r0...r15 告诉编译器汇编代码中修改了通用寄存器r0...r15
cc 告诉编译器汇编代码会导致CPU状态位的改变
memory 告诉编译器汇编代码会读取或修改内存中某个地址存放的值

对于“memory”来说,它并不是表示寄存器被读取或修改了,而是表示内存中的值被修改了。出于优化的目的,在执行你的汇编代码之前,编译器将某些变量的值还保存在寄存器中,并没有被写到实际的内存中。但是,如果你的汇编代码会读取内存中的值,则很有可能新的值还在寄存器中,而内存中存放的还是老的值,这样就会造成错误。添加了“memory”之后,编译器会在执行你的代码之前,保证将保存在寄存器中,没有更新到内存中的值全部都写入到内存中。

此列表中的每一项都要用双引号("")括起来,每项之间要用逗号(“,”)分割。

 

C/C++ 中嵌入 arm 汇编的更多相关文章

  1. 如何在C或C++代码中嵌入ARM汇编代码

    转载自:http://blog.csdn.net/roland_sun/article/details/42921131 大家知道,用C或者C++等高级语言编写的程序,会被编译器编译成最终的机器指令. ...

  2. arm汇编学习(四)

    一.android jni实现1.静态实现jni:先由Java得到本地方法的声明,然后再通过JNI实现该声明方法.2.动态实现jni:先通过JNI重载JNI_OnLoad()实现本地方法,然后直接在J ...

  3. linux内核分析作业4:使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用

    系统调用:库函数封装了系统调用,通过库函数和系统调用打交道 用户态:低级别执行状态,代码的掌控范围会受到限制. 内核态:高执行级别,代码可移植性特权指令,访问任意物理地址 为什么划分级别:如果全部特权 ...

  4. [汇编] C语言中嵌入汇编

    >_<" 下面是在C语言中嵌入汇编的例子,下面是三点要注意的~ 1.内联式汇编 2._asm关键字 3.并不是所有中断都能被支持 #include<iostream> ...

  5. ARM中C和汇编混合编程及示例(转)

    在嵌入式系统开发中,目前使用的主要编程语言是C和汇编,C++已经有相应的编译器,但是现在使用还是比较少的.在稍大规模的嵌入式软件中,例如含有OS,大部分的代码都是用C编写的,主要是因为C语言的结构比较 ...

  6. KEIL C51 中嵌入汇编以及C51与A51间的相互调用

    如何在 KEIL C51(v6.21) 中调用汇编函数的一个示例 有关c51调用汇编的方法已经有很多帖子讲到,但是一般只讲要点,很少有对整个过程作详细描述,对于初学者是不够的,这里笔者通过一个简单例子 ...

  7. 在C中嵌入汇编

    早前公布了C和汇编混编的温度控制器程序,收到一些朋友的询问,他们无法在自己程序中使用我的18B20的汇编子程序或无法正常通过混编后的程序编译. 其实在KEIL中嵌入汇编的方法很简单.如图一,在C文件中 ...

  8. 实验--使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用(杨光)

    使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用 攥写人:杨光  学号:20135233 ( *原创作品转载请注明出处*) ( 学习课程:<Linux内核分析>MOOC课程 ...

  9. 大脸猫讲逆向之ARM汇编中PC寄存器详解

    i春秋作家:v4ever 近日,在研究一些开源native层hook方案的实现方式,并据此对ARM汇编层中容易出问题的一些地方做了整理,以便后来人能有从中有所收获并应用于现实问题中.当然,文中许多介绍 ...

随机推荐

  1. 3P - Snooker

    background: Philip likes to play the QQ game of Snooker when he wants a relax, though he was just a ...

  2. JMD Handy Baby 2 to Decode & Adding New BMW 525 ID46 Key

    Here OBD2TOOL share the guide on how to use JMD Handy Baby II to decode and add new keys for BMW 525 ...

  3. 真·浅谈treap树

    treap树是一种平衡树,它有平衡树的性质,满足堆的性质,是二叉搜索树,但是我们需要维护他 为什么满足堆的性质?因为每个节点还有一个随机权值,按照随机权值维持这个堆(树),可以用O(logn)的复杂度 ...

  4. Vux项目搭建

    1.快速搭建项目模板 因为项目使用vux,所以推荐使用vux官网的airyland/vux2 模板,vue-cli工具是vue项目的搭建脚手架 默认为 webpack2 模板,如果你需要使用webpa ...

  5. 《C#从现象到本质》读书笔记(六)第8章委托和事件

    <C#从现象到本质>读书笔记(六)第二部分 C#特性 第8章委托和事件 从这一部分开始,知识点就相对少了,重要的是代码练习.奈何太高深的代码平常不怎么用,这些特性也不是经常写代码的. 委托 ...

  6. 下拉列表 通过option 改变div的内容

    <!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>& ...

  7. C#Dictionary源码

     Dictionary Dictionary与hashtable的区别:dictionary支持泛型. 通常处理哈希冲突的方法有:开放地址法,再哈希法,链地址法,建立一个公共栈区等. 在哈希表上进行查 ...

  8. 2019.03.01 bzoj3075: [Usaco2013]Necklace(kmp+dp)

    传送门 题意简述:给出S,TS,TS,T两个字串,∣S∣≤10000,∣T∣≤1000|S|\le10000,|T|\le1000∣S∣≤10000,∣T∣≤1000,问至少从SSS中删去几个字符能够 ...

  9. python基本数据类型之字符串(三)

    python基本数据类型之字符串(三) 转换和判断方法 在python中,有一些内置方法可以将字符串转化特定形式,而与之对应的一些方法可以判断字符串是否符合某些形式.因此,在这篇文章中,笔者把转换方法 ...

  10. Python开发——6.文件操作

    一.文件操作 1.文件操作的处理流程 打开文件得到文件句柄并赋值给一个变量====>通过句柄对文件进行分析====>关闭文件 #1. 打开文件,得到文件句柄并赋值给一个变量 f=open( ...