day10 ARM伪指令、ARM混合调用
2017-06-16 17:20
232 查看
回顾:
面试题:谈谈对ARM处理器的认识
1.常见的处理器架构
2.ARM的定义
3.ARM的版本和流水线
4.ARM的7种工作模式
5.ARM的2种工作状态
6.ARM的37个寄存器
7.ARM的7种异常
8.ARM的异常处理流程(核心)
9.ARM指令
1.移位操作符
2.影响cpsr的两种情形
3.CPU核跳转的三种方式
4.分支跳转指令b/bl
5.数据处理指令
数据传送指令mov/mvn
算数运算指令add/adc/sub/sbc
位运算指令and/orr/eor/bic
比较指令cmp/cmn/tst/teq
6.存储加载指令:ldr/str
6.1.明确:数据处理指令(四大类)仅仅在ARM核
内部玩,操作的对象是ARM寄存器立即数
数据处理指令不会和外设进行数据交互
问:汇编中如何实现CPU核和外设数据交互呢?
答:利用存储加载指令
6.2.明确:数据加载指令用于实现CPU核和外设的
数据交互,数据的传输(双向),可以加载
也可以存储
问:何为加载,何为存储呢?
答:
加载:数据是从外设到CPU核内部寄存器
存储:数据是从CPU核内部寄存器到外设
即:
加载指令为ldr
存储指令为str
6.3.明确:CPU核访问外设都是以地址的形式访问
也就是直到了外设的地址,CPU核即可访问
6.3.加载存储指令ldr,str
ldr指令作用:将外设中的数据加载到CPU核寄存器中
str指令作用:将CPU核中的数据存储到外设中
例如:
mov r0, #0x48000004 @假设成立,结果:r0=0x48000000
ldr r1, [r0] @以r0寄存器中的数0x48000000为地址
从这个地址中(内存的存储空间)取出
4字节数据放到ARM核寄存器r1中
add r1, r1, #1 @r1=r1+1
str r1, [r0] @将r1寄存器中的数据存储到以
r0寄存器中的数据为地址的外设中
务必画出以str的存储操作示意图!
7.ARM核栈操作指令
7.1.明确:栈的本质和功能
栈本质就是某块内存存储区域
栈用来保存数据(临时变量,函数参数)
栈的操作就是压栈和出栈
ARM核寄存器sp保存栈指针
7.2.ARM核栈的分类
减栈:sp向内存地址减小的方向变化
加栈:sp向内存地址增加的方向变化
空栈:先压数后调整sp
满栈:先调整sp后压数
总结:由以上四种基本栈有得到组合形式:
满减栈/满加栈/空减栈/空加栈
只掌握满减栈即可!
7.3.满减栈的操作指令
老ARM核(ARMV7之前版本),满减栈的操作指令为:stmfd/ldmfd
新ARM核(ARMV7以后版本),满减栈的操作指令为:push/pop
注意:ARM核小标号的寄存器数据放到内存的低地址处理!
例如:
老写法:
压栈:stmfd sp!, {r4-r7,lr}
说明:将ARM寄存器r4,r5,r6,r7,lr的数据保存在栈中
出栈:ldmfd sp!, {r4-r7,pc}
说明:从栈中将数据恢复到r4,r5,r6,r7,pc寄存器中
新写法:
压栈:push {r4-r7,lr}
说明:将ARM寄存器r4,r5,r6,r7,lr的数据保存在栈中
出栈: pop {r4-r7,pc}
说明:从栈中将数据恢复到r4,r5,r6,r7,pc寄存器中
务必举例子画出压栈和出栈的操作示意图!
8.状态寄存器(cpsr/spsr)访问指令mrs/msr
明确:掌握cpsr的两个重要域
f域:cpsr的bit[31:24]
c域:cpsr的bit[8:0]
例如:
mrs r0, cpsr @将cpsr的值保存在r0寄存器中
mrs r1, spsr @将spsr的值保存在r1寄存器中
msr cpsr, r0 @将r0寄存器中的数据写到cpsr
msr cpsr_f, r0 @将r0寄存器中的数据写到cpsr
但仅仅影响f域
9.ARM伪指令
9.1.明确:ARM伪指令CPU核是不能直接识别的
更不能去直接运行的,首先需要汇编器(arm...as)
进行翻译,最终翻译成CPU核识别的真实指令
9.2.ARM伪指令之adr/adrl
adr指令用于地址加载
加载地址范围:+/-1020bytes
加载地址的范围是相对于PC
案例:通过反汇编掌握adr指令的操作
mkdir /opt/arm/day10/1.0 -p
cd /opt/arm/day10/1.0
vim adr.s 添加如下内容
.text
.code 32
.global _start
_start:
adr r0, Delay @将Delay标签的地址加载到
r0寄存器中,也可以认为是
将Delay标签中第一条指令的
地址加载到r0中
问:adr伪指令到底由汇编器
翻译成了什么模样呢?
mov r1, #1
add r1, r1, #15
Delay:
mov r1, #4
.end
保存退出
arm-cortex_a9-linux-gnueabi-as -g -o adr.o adr.s
arm-cortex_a9-linux-gnueabi-objdump -D adr.o > adr.dis
vim adr.dis
00000000 <_start>: @入口函数_start的地址为0x00000000
0: e28f0004 add r0, pc, #4
4: e3a01001 mov r1, #1
8: e281100f add r1, r1, #15
0000000c <Delay>: @Delay标签的地址为0xc
c: e3a01004 mov r1, #4
说明:
第一列表示每条指令对应的内存存储地址
第二列表示每条指令对应的机器码(给CPU核用)
第三列表示每条机器码对应的汇编代码(给人用)
分析:
1.源码中第一条指令为adr,通过汇编器翻译成了add真实指令
2.adr指令的功能就是将Delay标签的地址加载到r0
中,运算实现过程:
1.当这条指令add r0, pc, #4执行时
pc=8,add执行以后,r0=pc+4=8+4=12=0xC
2.0xc这个数对应的就是mov r1, #4这条的
的地址,并且这条指令的地址和Delay标签的
地址一样都是0xC
至此也就验证了adr指令就是用于加载地址
9.3.ARM伪指令之ldr
1.问:ldr到底是真实指令还是伪指令呢?
答:关键看ldr使用如何
2.ldr作为伪指令的三种使用方式
方式一:
mov r0, #0x1ff @不合法,编译不通过,立即数超范围
ldr r0, =0x1ff @合法,编译通过
@将立即数放到r0寄存器中
@此时的ldr为伪指令
方式二:
ldr r0, =testdata @将testdata标签对应的地址
加载到r0寄存器中
也就是r0保存分配的四字节内存空间的首地址
@此时ldr为伪指令
ldr r1, [r0] @以r0寄存器中的数据为地址
从这个地址中取出4字节的数据
放到r1寄存器
r1=0x12345678
@此时ldr为真实指令
...
testdata:@testdata就是分配的4字节内存空间的首地址
.word 0x12345678 @分配4字节的内存空间
@并且将这块内存空间初始化为
0x12345678
@.word类似int
方式三:
ldr r0, testdata @将以testdata标签对应的地址
直接从这个地址中取出数据给r0
r0=0x12345678
@此时ldr为伪指令
...
testdata:
.word 0x1234578
案例:编写汇编代码,然后反汇编,掌握ldr伪指令
cd /opt/arm/day10/2.0
vim ldr.s 添加如下内容
.text
.code 32
.global _start
_start:
ldr r0, =testdata
ldr r1, [r0]
mov r2, #15
add r2, r2, #1
testdata:
.word 0x12345678
.end
保存退出
arm-cortex_a9-linux-gnueabi-as -g -o ldr.o ldr.s
arm-cortex_a9-linux-gnueabi-objdump -D ldr.o > ldr.dis
vim ldr.dis //分析反汇编代码
结论:
1.ldr伪指令最终翻译的真实指令还是ldr
再例如:
ldr pc, jmp_table
jmp_table:
.word func_addr
结果:让CPU核跑到func_addr地址处继续运行(F->D->E->M->W)
10.ARM伪操作(目前共121个伪操作)
.text/.data/.bss/.global/.extern/.byte
/.word/.int/.long/.string/.ascii/.asciz
/.equ/.skip/.space等
例如:
.equ TEST_NUM, #0x20
等价于:
#define TEST_NUM 0x20
.ascii "hello,world\0"
或者
.asciz "hello,world"
或者
.string "hello,world" @分配一块存储区域
并且初始化为“hello,world”
问:如何获取这块存取区域的首地址呢?
答:只需加一个标签即可,标签就是这块存储区域的首地址
str1: @char *str1 = "hello"
.asciz "hello"
str2: @char *str2 = "hfllo"
.asciz "hfllo"
案例:利用汇编实现两个字符串的比较
回顾C实现的字符串比较
str1 = "hello";
str2 = "hfllo"
int my_strcmp(const char *str1,
const char *str2)
{
while(*str1) {
if(*str1 != *str2)
return *str1 - *str2;
str1++;
str2++;
}
return *str1 - *str2;
}
实施步骤:
mkdir /opt/arm/day10/3.0
vim strcmp.s 添加如下内容
.text
.code 32
.global _start
_start:
ldr r0, =str1 @r0保存字符串str1的首地址
ldr r1, =str2 @r1保存字符串str2的首地址
loop:
ldrb r2, [r0], #1 @取出字符串的单个字符数据
ldrb r3, [r1], #1
cmp r2, #0
beq cmp_end
cmp r2, r3
beq loop
cmp_end:
sub r0, r2, r3
b .
@.ascii:分配一块内存空间,存放字符串数据信息
str1: @相当于给"hello\0"定义一个首地址
.ascii "hello\0" @char *str1 = "hello";
str2: @相当于给"hfllo\0"定义一个首地址
.ascii "hfllo\0" @char *str2 = "hfllo";
.end
编译利用qemu测试,通过观察r0寄存器的值判断大小
11.ARM的混合调用:C调用汇编/汇编调用C
11.1.明确:以下存储器设备的访问速度
ARM核寄存器>Cache缓存->内存>闪存
11.2.明确:函数之间传递参数的方式方法两种:
1.默认采用ARM核寄存器传递参数(ARM核寄存器速度快)
注意:ARM核寄存器中能够传参参数的寄存器只有四个:
r0/r1/r2/r3
注意:函数的返回值用r0寄存器保存
例如:
int add(int a, int b, int c, int d){
r0 r1 r2 r3
return a+b+c+d; //r0=r0+r1+r2+r3
}
注意:如果参数的个数超过四个,用栈进行传递,但是栈(内存)的访问速度势必要比ARM核寄存器的访问速度要慢,所以尽量让参数的个数小于4,如果大于4,让访问次数的参数放在最前面!
int add(int a, int b, int c, int d, int e, int f){
r0 r1 r2 r3 栈 栈
return a+b+c+d+e+f; //r0=r0+r1+r2+r3
}
2.在linux内核中,强制在函数前面加关键字asmlinkage要求函数传递参数使用栈,而无需ARM寄存器 。
asmlinkage int add(int a, int b, int c...);
给add传递参数强制使用栈!
问:为何不用寄存器呢(速度快)?
答:由于ARM核寄存器的个数太少了,linux内核尽量让ARM核寄存器在一些效率要求特别高的场合才能使用:
reigster struct task_struct *current;
告诉编译器,单独指定一个ARM寄存器来保存current的值,将来访问效率提高!
面试题:谈谈对ARM处理器的认识
1.常见的处理器架构
2.ARM的定义
3.ARM的版本和流水线
4.ARM的7种工作模式
5.ARM的2种工作状态
6.ARM的37个寄存器
7.ARM的7种异常
8.ARM的异常处理流程(核心)
9.ARM指令
1.移位操作符
2.影响cpsr的两种情形
3.CPU核跳转的三种方式
4.分支跳转指令b/bl
5.数据处理指令
数据传送指令mov/mvn
算数运算指令add/adc/sub/sbc
位运算指令and/orr/eor/bic
比较指令cmp/cmn/tst/teq
6.存储加载指令:ldr/str
6.1.明确:数据处理指令(四大类)仅仅在ARM核
内部玩,操作的对象是ARM寄存器立即数
数据处理指令不会和外设进行数据交互
问:汇编中如何实现CPU核和外设数据交互呢?
答:利用存储加载指令
6.2.明确:数据加载指令用于实现CPU核和外设的
数据交互,数据的传输(双向),可以加载
也可以存储
问:何为加载,何为存储呢?
答:
加载:数据是从外设到CPU核内部寄存器
存储:数据是从CPU核内部寄存器到外设
即:
加载指令为ldr
存储指令为str
6.3.明确:CPU核访问外设都是以地址的形式访问
也就是直到了外设的地址,CPU核即可访问
6.3.加载存储指令ldr,str
ldr指令作用:将外设中的数据加载到CPU核寄存器中
str指令作用:将CPU核中的数据存储到外设中
例如:
mov r0, #0x48000004 @假设成立,结果:r0=0x48000000
ldr r1, [r0] @以r0寄存器中的数0x48000000为地址
从这个地址中(内存的存储空间)取出
4字节数据放到ARM核寄存器r1中
add r1, r1, #1 @r1=r1+1
str r1, [r0] @将r1寄存器中的数据存储到以
r0寄存器中的数据为地址的外设中
务必画出以str的存储操作示意图!
7.ARM核栈操作指令
7.1.明确:栈的本质和功能
栈本质就是某块内存存储区域
栈用来保存数据(临时变量,函数参数)
栈的操作就是压栈和出栈
ARM核寄存器sp保存栈指针
7.2.ARM核栈的分类
减栈:sp向内存地址减小的方向变化
加栈:sp向内存地址增加的方向变化
空栈:先压数后调整sp
满栈:先调整sp后压数
总结:由以上四种基本栈有得到组合形式:
满减栈/满加栈/空减栈/空加栈
只掌握满减栈即可!
7.3.满减栈的操作指令
老ARM核(ARMV7之前版本),满减栈的操作指令为:stmfd/ldmfd
新ARM核(ARMV7以后版本),满减栈的操作指令为:push/pop
注意:ARM核小标号的寄存器数据放到内存的低地址处理!
例如:
老写法:
压栈:stmfd sp!, {r4-r7,lr}
说明:将ARM寄存器r4,r5,r6,r7,lr的数据保存在栈中
出栈:ldmfd sp!, {r4-r7,pc}
说明:从栈中将数据恢复到r4,r5,r6,r7,pc寄存器中
新写法:
压栈:push {r4-r7,lr}
说明:将ARM寄存器r4,r5,r6,r7,lr的数据保存在栈中
出栈: pop {r4-r7,pc}
说明:从栈中将数据恢复到r4,r5,r6,r7,pc寄存器中
务必举例子画出压栈和出栈的操作示意图!
8.状态寄存器(cpsr/spsr)访问指令mrs/msr
明确:掌握cpsr的两个重要域
f域:cpsr的bit[31:24]
c域:cpsr的bit[8:0]
例如:
mrs r0, cpsr @将cpsr的值保存在r0寄存器中
mrs r1, spsr @将spsr的值保存在r1寄存器中
msr cpsr, r0 @将r0寄存器中的数据写到cpsr
msr cpsr_f, r0 @将r0寄存器中的数据写到cpsr
但仅仅影响f域
9.ARM伪指令
9.1.明确:ARM伪指令CPU核是不能直接识别的
更不能去直接运行的,首先需要汇编器(arm...as)
进行翻译,最终翻译成CPU核识别的真实指令
9.2.ARM伪指令之adr/adrl
adr指令用于地址加载
加载地址范围:+/-1020bytes
加载地址的范围是相对于PC
案例:通过反汇编掌握adr指令的操作
mkdir /opt/arm/day10/1.0 -p
cd /opt/arm/day10/1.0
vim adr.s 添加如下内容
.text
.code 32
.global _start
_start:
adr r0, Delay @将Delay标签的地址加载到
r0寄存器中,也可以认为是
将Delay标签中第一条指令的
地址加载到r0中
问:adr伪指令到底由汇编器
翻译成了什么模样呢?
mov r1, #1
add r1, r1, #15
Delay:
mov r1, #4
.end
保存退出
arm-cortex_a9-linux-gnueabi-as -g -o adr.o adr.s
arm-cortex_a9-linux-gnueabi-objdump -D adr.o > adr.dis
vim adr.dis
00000000 <_start>: @入口函数_start的地址为0x00000000
0: e28f0004 add r0, pc, #4
4: e3a01001 mov r1, #1
8: e281100f add r1, r1, #15
0000000c <Delay>: @Delay标签的地址为0xc
c: e3a01004 mov r1, #4
说明:
第一列表示每条指令对应的内存存储地址
第二列表示每条指令对应的机器码(给CPU核用)
第三列表示每条机器码对应的汇编代码(给人用)
分析:
1.源码中第一条指令为adr,通过汇编器翻译成了add真实指令
2.adr指令的功能就是将Delay标签的地址加载到r0
中,运算实现过程:
1.当这条指令add r0, pc, #4执行时
pc=8,add执行以后,r0=pc+4=8+4=12=0xC
2.0xc这个数对应的就是mov r1, #4这条的
的地址,并且这条指令的地址和Delay标签的
地址一样都是0xC
至此也就验证了adr指令就是用于加载地址
9.3.ARM伪指令之ldr
1.问:ldr到底是真实指令还是伪指令呢?
答:关键看ldr使用如何
2.ldr作为伪指令的三种使用方式
方式一:
mov r0, #0x1ff @不合法,编译不通过,立即数超范围
ldr r0, =0x1ff @合法,编译通过
@将立即数放到r0寄存器中
@此时的ldr为伪指令
方式二:
ldr r0, =testdata @将testdata标签对应的地址
加载到r0寄存器中
也就是r0保存分配的四字节内存空间的首地址
@此时ldr为伪指令
ldr r1, [r0] @以r0寄存器中的数据为地址
从这个地址中取出4字节的数据
放到r1寄存器
r1=0x12345678
@此时ldr为真实指令
...
testdata:@testdata就是分配的4字节内存空间的首地址
.word 0x12345678 @分配4字节的内存空间
@并且将这块内存空间初始化为
0x12345678
@.word类似int
方式三:
ldr r0, testdata @将以testdata标签对应的地址
直接从这个地址中取出数据给r0
r0=0x12345678
@此时ldr为伪指令
...
testdata:
.word 0x1234578
案例:编写汇编代码,然后反汇编,掌握ldr伪指令
cd /opt/arm/day10/2.0
vim ldr.s 添加如下内容
.text
.code 32
.global _start
_start:
ldr r0, =testdata
ldr r1, [r0]
mov r2, #15
add r2, r2, #1
testdata:
.word 0x12345678
.end
保存退出
arm-cortex_a9-linux-gnueabi-as -g -o ldr.o ldr.s
arm-cortex_a9-linux-gnueabi-objdump -D ldr.o > ldr.dis
vim ldr.dis //分析反汇编代码
结论:
1.ldr伪指令最终翻译的真实指令还是ldr
再例如:
ldr pc, jmp_table
jmp_table:
.word func_addr
结果:让CPU核跑到func_addr地址处继续运行(F->D->E->M->W)
10.ARM伪操作(目前共121个伪操作)
.text/.data/.bss/.global/.extern/.byte
/.word/.int/.long/.string/.ascii/.asciz
/.equ/.skip/.space等
例如:
.equ TEST_NUM, #0x20
等价于:
#define TEST_NUM 0x20
.ascii "hello,world\0"
或者
.asciz "hello,world"
或者
.string "hello,world" @分配一块存储区域
并且初始化为“hello,world”
问:如何获取这块存取区域的首地址呢?
答:只需加一个标签即可,标签就是这块存储区域的首地址
str1: @char *str1 = "hello"
.asciz "hello"
str2: @char *str2 = "hfllo"
.asciz "hfllo"
案例:利用汇编实现两个字符串的比较
回顾C实现的字符串比较
str1 = "hello";
str2 = "hfllo"
int my_strcmp(const char *str1,
const char *str2)
{
while(*str1) {
if(*str1 != *str2)
return *str1 - *str2;
str1++;
str2++;
}
return *str1 - *str2;
}
实施步骤:
mkdir /opt/arm/day10/3.0
vim strcmp.s 添加如下内容
.text
.code 32
.global _start
_start:
ldr r0, =str1 @r0保存字符串str1的首地址
ldr r1, =str2 @r1保存字符串str2的首地址
loop:
ldrb r2, [r0], #1 @取出字符串的单个字符数据
ldrb r3, [r1], #1
cmp r2, #0
beq cmp_end
cmp r2, r3
beq loop
cmp_end:
sub r0, r2, r3
b .
@.ascii:分配一块内存空间,存放字符串数据信息
str1: @相当于给"hello\0"定义一个首地址
.ascii "hello\0" @char *str1 = "hello";
str2: @相当于给"hfllo\0"定义一个首地址
.ascii "hfllo\0" @char *str2 = "hfllo";
.end
编译利用qemu测试,通过观察r0寄存器的值判断大小
11.ARM的混合调用:C调用汇编/汇编调用C
11.1.明确:以下存储器设备的访问速度
ARM核寄存器>Cache缓存->内存>闪存
11.2.明确:函数之间传递参数的方式方法两种:
1.默认采用ARM核寄存器传递参数(ARM核寄存器速度快)
注意:ARM核寄存器中能够传参参数的寄存器只有四个:
r0/r1/r2/r3
注意:函数的返回值用r0寄存器保存
例如:
int add(int a, int b, int c, int d){
r0 r1 r2 r3
return a+b+c+d; //r0=r0+r1+r2+r3
}
注意:如果参数的个数超过四个,用栈进行传递,但是栈(内存)的访问速度势必要比ARM核寄存器的访问速度要慢,所以尽量让参数的个数小于4,如果大于4,让访问次数的参数放在最前面!
int add(int a, int b, int c, int d, int e, int f){
r0 r1 r2 r3 栈 栈
return a+b+c+d+e+f; //r0=r0+r1+r2+r3
}
2.在linux内核中,强制在函数前面加关键字asmlinkage要求函数传递参数使用栈,而无需ARM寄存器 。
asmlinkage int add(int a, int b, int c...);
给add传递参数强制使用栈!
问:为何不用寄存器呢(速度快)?
答:由于ARM核寄存器的个数太少了,linux内核尽量让ARM核寄存器在一些效率要求特别高的场合才能使用:
reigster struct task_struct *current;
告诉编译器,单独指定一个ARM寄存器来保存current的值,将来访问效率提高!
相关文章推荐
- 嵌入式ARM系统实战开发(编程模型、指令系统、程序设计、混合编程、驱动开发)视频教程
- ARM / Thumb 指令混合编程之代码交织 ( interworking )
- 嵌入式ARM系统实战开发(编程模型、指令系统、程序设计、混合编程、驱动开发)
- ARM / Thumb 指令混合编程之代码交织 ( interworking )
- ARM指令中的函数调用
- ARM:伪指令、伪操作、C和汇编混合操作、汇编控制LED
- day11 ARM混合调用案例、ARM核 异常处理流程、软件处理异常
- 基于ARM9:Thumb指令系统和ARM指令系统
- C# 调用cmd执行指令
- matlab/C混合编程--mex文件的编写、编译、调用
- 【转】使用JNI进行混合编程:在C/C++中调用Java代码
- ARM指令STMFD和LDMFD
- ARM指令教程
- 分析函数调用的汇编指令
- ARM 汇编伪指令
- ARM 汇编伪指令宏的用法详解(MACRO-MEND)
- ARM指令中如何判断一个立即数是有效立即数
- ARM乘法指令与乘加指令
- 13.7 混合使用库函数和系统调用进行文件I/O
- ARM中C和汇编混合编程