-
Notifications
You must be signed in to change notification settings - Fork 3
/
arm_asm_sample.c
executable file
·253 lines (226 loc) · 11.8 KB
/
arm_asm_sample.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
#include <stdio.h>
/*
不同的C编译器内联汇编代码时,它们的写法是各不相同的,下面为gcc写法
内嵌汇编语法如下,其中汇编语句模板必不可少,其他三部分可选,如果使用了后面的部分,而前面部分为空,也需要用“:”格开,相应部分内容为空
asm(
asmc ode: 由汇编语句序列组成,语句之间使用“;”分开,指令中的操作数可以使用占位符引用C语言变量,操作数占位符最多10个,名称如下:%0,%1…,%9。
output: 输出部分描述输出操作数,不同的操作数描述符之间用逗号格开,每个操作数描述符由限定字符串和C语言变量组成。每个输出操作数的限定字符串必须包含“=”表示他是一个输出操作数。
input: 输入部分描述输入操作数,不同的操作数描述符之间使用逗号格开,每个操作数描述符由限定字符串和C语言表达式或者C语言变量组成。
clobber:这部分常常以“memory”为约束条件,以表示操作完成后内存中的内容已有改变,如果原来某个寄存器的内容来自内存,那么现在内存中这个单元的内容已经改变。
)
操作符 含义
r 通用寄存器r0-r15,表示需要将变量与某个通用寄存器相关联,先将变量的值读入寄存器,然后在指令中使用相应寄存器
m 一个有效的内存地址,表示操作数是在内存中,在编译时将直接使用内存中的变量,而不会像"r"操作符一样将其读入寄存器,一般是使用对内存进行操作的指令时会才使用
I 数据处理指令中的立即数
X 被修饰的操作符只能用作输出
修饰符 含义
无 被修饰的操作符是只读的,输入部分必须为read-only,C编译器是没有能力做这个检查
= 被修饰的操作符只写, 输出部分必须为write-only,相应C表达式的值必须是左值,C编译器是没有能力做这个检查
+ 被修饰的操作符具有可读可写的属性
& 被修饰的操作符能被作为输出
clobber 含义
memory 表示修改了内存中的数据,这将强迫编译器在执行汇编代码前存储所有缓存的值,然后在执行完汇编代码后重新加载该值
rn 表示修改了寄存器rn(n寄存器标号)
cc 表示修改了cpsr等标志类寄存器,它用来向编译器指明,内嵌汇编指令改变了内存中的值。这将强迫编译器在执行汇编代码前存储所有缓存的值,然后在执行完汇编代码后重新加载该值
ATPCS调用规则
__asm__是GCC关键字asm的宏定义:
#define __asm__ asm
__asm__或asm用来声明一个内联汇编表达式,所以任何一个内联汇编表达式都以它开头,是必不可少的。
__volatile__是GCC关键字volatile的宏定义:
#define __volatile__ volatile
__volatile__或volatile是可选的,如果用了它,则向GCC声明不允许对该内联汇编优化,否则,当使用优化选项(-o)进行编译时GCC会根据字自己的判断决定是否将内联汇编表达式的指令优化掉。
__asm__ __volatile__(“” ::: ”memory”),
#define barrier() __asm____volatile__("": : :"memory")
clobber 通常用于声明被修改的部分,比如使用内嵌汇编修改了变量,则通常需要在该部分加入"memory"声明,如果修改是cpsr, 则需要加入"cc"声明, 如果是修改了寄存器,则需要声明修改了哪些寄存器,通常情况下,如果想要编译器不使用某个寄存器,通过
在clobber声明寄存器即可避免此种情况发生,比如我不想寄存器使用r0,则在clobber中声明r0,那么编译器则会选择其他寄存器
调试流程:
1、设置断点在main函数 b main
2、将程序运行到 asm_test_input 处 run
3、汇编级执行 stepi n(n是运行的步数,如果不写则默认1步)
4、打印内存 x /nxw addr (笔者使用命令来打印堆栈中的数值,其中n是打印个数,具体请参靠gdb的命令说明)
5、打印寄存器 info register
6、打印汇编 disassemble
ARM嵌入式开发中的GCC内联汇编__asm__:https://www.cnblogs.com/fengliu-/p/7667892.html
__asm__ __volatile__内嵌汇编用法简述:https://blog.csdn.net/geekcome/article/details/6216436
ARM汇编-从内嵌汇编开始:https://blog.csdn.net/u011298001/article/details/83864516
ARM GCC 内嵌(inline)汇编手册:http://blog.chinaunix.net/uid-20543672-id-3194385.html
*/
/*
它向GCC声明,内存做了改动,GCC在编译的时候,会将此因素考虑进去。在访问IO端口和IO内存时,会用到内存屏障。它就是防止编译器对读写IO端口和IO内存指令的优化而实际的错误。
其原理是保留程序的执行顺序,因为在使用了带有memory clobber的asm声明后,所有变量的内容都是不可预测的。编译器将按顺序编译语句
*/
# define barrier() _asm__volatile_("": : :"memory")//
void asm_mem_to_cpsr(void)
{
int cpsr_status = 0x80;
__asm__ __volatile__(
"msr cpsr,%0"
:
: "r" (cpsr_status)//声明输入
: "cc"
);
/*
0x000103d6 <+2>: sub sp, #12
0x000103d8 <+4>: add r7, sp, #0
0x000103da <+6>: movs r3, #128 ; 0x80
0x000103dc <+8>: str r3, [r7, #4]
0x000103de <+10>: ldr r3, [r7, #4]
0x000103e0 <+12>: msr CPSR_fc, r3
0x000103e4 <+16>: nop
*/
}
void asm_cpsr_to_mem(void)
{
int cpsr_status = 0;
asm (
"mrs %0, cpsr;"
:"=r" (cpsr_status)//声明输出
:
: "memory"//告诉编译器内存已经被修改了
);
printf("[%s](%d)cpsr_status = %#x\n",__func__, __LINE__, cpsr_status);//这里比较奇怪,打印出来的值跟gdb读取到的值有些许不同
}
/* 仅讲解两种方法,从内存到内存和从寄存器到内存,因为内存到寄存器和寄存器到寄存器在我们的实际应用中作用不大,我们需要的是将值放在内存中以供我们后续使用 */
void asm_mem_to_mem(void)
{
unsigned int var_1 = 0;
unsigned int var_2 = 999;
unsigned int var_3 = 0;
__asm__ __volatile__
(
"mov %0, #20;"
"mov %2, %1;"
: "=r" (var_1), "=r"(var_3)//r表示使用任何寄存器,=号表示只写,如果不添加 = 号,编译会提示缺失 = 号,output operand constraint lacks ‘=’
: "r" (var_2)
: "memory"//memory表示修改了内存,一般情况下还有r1, r2等来表示寄存器被修改
);
/* 笔者心得,在输出部分一般情况下都需要加入"=r", 其表示输出部分可写且需要使用寄存器来传递值 */
printf("[%s](%d)var_1 = %d, var_2 = %d, var_3 = %d\n",__func__, __LINE__, var_1, var_2, var_3);
}
void asm_register_to_mem(int param_0, int param_1, int param_2)
{
int var_0 = 0;
int var_1 = 0;
int var_2 = 0;
/* 1. 错误例子,没有声明寄存器被修改 */
__asm__ __volatile__
(
"str r0, [%0];" //r0 -> var_0
"str r1, [%1];" //r1 -> var_1
"str r2, [%2];" //r2 -> var_2
:
:"r"(&var_0), "r"(&var_1), "r"(&var_2)
:"memory"
);
/*
0x000103c8 <+4>: add r7, sp, #0
0x000103ca <+6>: str r0, [r7, #12]
0x000103cc <+8>: str r1, [r7, #8]
0x000103ce <+10>: str r2, [r7, #4]
0x000103d0 <+12>: movs r3, #0
0x000103d2 <+14>: str r3, [r7, #28]
0x000103d4 <+16>: movs r3, #0
0x000103d6 <+18>: str r3, [r7, #24]
0x000103d8 <+20>: movs r3, #0
0x000103da <+22>: str r3, [r7, #20]
0x000103dc <+24>: add.w r3, r7, #28
0x000103e0 <+28>: add.w r2, r7, #24//r2被修改
0x000103e4 <+32>: add.w r1, r7, #20//r1被修改
0x000103e8 <+36>: str r0, [r3, #0]
0x000103ea <+38>: str r1, [r2, #0]
0x000103ec <+40>: str r2, [r1, #0]
*/
/* 2. 正确例子*/
__asm__ __volatile__
(
"str r0, [%0];" //r0 -> var_0
"str r1, [%1];" //r1 -> var_1
"str r2, [%2];" //r2 -> var_2
:
:"r"(&var_0), "r"(&var_1), "r"(&var_2)
:"memory", "r0", "r1", "r2"
);
/*
0x0001046c <+4>: add r7, sp, #8
0x0001046e <+6>: str r0, [r7, #12]
0x00010470 <+8>: str r1, [r7, #8]
0x00010472 <+10>: str r2, [r7, #4]
0x00010474 <+12>: movs r3, #0
0x00010476 <+14>: str r3, [r7, #28]
0x00010478 <+16>: movs r3, #0
0x0001047a <+18>: str r3, [r7, #24]
0x0001047c <+20>: movs r3, #0
0x0001047e <+22>: str r3, [r7, #20]
0x00010480 <+24>: add.w r3, r7, #28 //r0没有被修改,编译器转而使用r3来进行操作
0x00010484 <+28>: add.w r4, r7, #24 //r1没有被修改,编译器转而使用r4来进行操作
0x00010488 <+32>: add.w r5, r7, #20 //r2没有被修改,编译器转而使用r5来进行操作
0x0001048c <+36>: str r0, [r3, #0] //将r0的值装入内存中的变量
0x0001048e <+38>: str r1, [r4, #0] //将r1的值装入内存中的变量
0x00010490 <+40>: str r2, [r5, #0] //将r2的值装入内存中的变量
*/
/* 3. 错误例子,没有声明寄存器被修改*/
__asm__ __volatile__
(
"mov %0, r0;" //r0 -> var_0
"mov %1, r1;" //r1 -> var_1
"mov %2, r2;" //r2 -> var_2
:"=r"(var_0), "=r"(var_1), "=r"(var_2) //输入部分,表示变量var输入到r0-r15中的一个寄存器
:
:"memory"
);
/*
0x00010470 <+8>: str r1, [r7, #8]
0x00010472 <+10>: str r2, [r7, #4]
0x00010474 <+12>: movs r3, #0
0x00010476 <+14>: str r3, [r7, #28]
0x00010478 <+16>: movs r3, #0
0x0001047a <+18>: str r3, [r7, #24]
0x0001047c <+20>: movs r3, #0
0x0001047e <+22>: str r3, [r7, #20]
0x00010480 <+24>: mov r1, r0 //r0被修改
0x00010482 <+26>: mov r2, r1 //r1被修改
0x00010484 <+28>: mov r3, r2 //r2被修改
0x00010486 <+30>: str r1, [r7, #28]
0x00010488 <+32>: str r2, [r7, #24]
0x0001048a <+34>: str r3, [r7, #20]
0x0001048c <+36>: ldr r3, [r7, #20]
0x0001048e <+38>: str r3, [sp, #4]
0x00010490 <+40>: ldr r3, [r7, #24]
0x00010492 <+42>: str r3, [sp, #0]
0x00010494 <+44>: ldr r3, [r7, #28]
*/
/* 4. 正确例子*/
__asm__ __volatile__
(
"mov %0, r0;" //r0 -> var_0
"mov %1, r1;" //r1 -> var_1
"mov %2, r2;" //r2 -> var_2
:"=r"(var_0), "=r"(var_1), "=r"(var_2) //输入部分,表示变量var输入到r0-r15中的一个寄存器
:
:"memory", "r0", "r1", "r2"//告诉编译器,寄存器r0, r1, r2已经被修改了。不要将r0, r1, r2用于操作
);
/*
0x000103c6 <+2>: sub sp, #36 ; 0x24
0x000103c8 <+4>: add r7, sp, #0
0x000103ca <+6>: str r0, [r7, #12]
0x000103cc <+8>: str r1, [r7, #8]
0x000103ce <+10>: str r2, [r7, #4]
0x000103d0 <+12>: movs r3, #0
0x000103d2 <+14>: str r3, [r7, #28]
0x000103d4 <+16>: movs r3, #0
0x000103d6 <+18>: str r3, [r7, #24]
0x000103d8 <+20>: movs r3, #0
0x000103da <+22>: str r3, [r7, #20]
0x000103dc <+24>: mov r5, r0
0x000103de <+26>: mov r4, r1 //r1没有被修改,编译器转而使用r4来进行操作
0x000103e0 <+28>: mov r3, r2 //r2没有被修改,编译器转而使用r3来进行操作
0x000103e2 <+30>: str r5, [r7, #28]
0x000103e4 <+32>: str r4, [r7, #24]
0x000103e6 <+34>: str r3, [r7, #20]
*/
printf("[%s](%d)var_0 = %d, var_1 = %d, var_2 = %d\n",__func__, __LINE__, var_0, var_1, var_2);
}
int main()
{
asm_register_to_mem(128, 256, 512);
}