EDA365电子论坛网
标题: ARM的BX指令 [打印本页]
作者: mutougeda 时间: 2020-10-14 15:37
标题: ARM的BX指令
bx的语法格式
" x+ ] B' G% P& J' jBX{<cond>} <Rm>
% _7 Q7 H1 P$ v+ \ W& e<cond>为指令执行的条件码。当<cond>忽略时指令为无条件执行。; G k/ J' K' y i* ]& v
<Rm>该寄存器中为跳转的目标地址。当<Rm>寄存器的bit[0]为0时,目标地址处的指令为ARM指令;
3 g' ]9 I1 l' K- y2 y, M( r6 T当<Rm>寄存器的bit[0]为1时,目标地址处的指令为Thumb指令。
; H# V) t X+ Y& N/ d. l- _' B0 e0 P; F# k+ e0 l) N9 q0 \- A* D
看一段简单程序的片段,不用考虑省略号的内容,看框架就可以了!
( \+ m6 O6 ?8 T) c. \; r2 m9 d6 q1 q# W7 S3 F/ e5 a$ i t3 z! H- e y
EXPORT LEDTEST
, Z5 ]0 c1 G; I7 m0 o% S; B3 D AREA testasm,CODE,READONLY
% w- R! F6 [1 q H( R9 R CODE327 E O! D2 p6 M# Y: r2 u+ c0 J& }
LEDTEST
2 b" _) N7 L" v5 s4 H& Q 。。。。。。。。。。* x# I3 t; ]$ W. T7 h
。。。。。。。。。
2 s8 C/ N( M2 @ 。。。。。。。。
& ]$ B- ^0 M0 Z0 H6 ^ 。。。。。。。。。9 v) z+ J: L Q. ?+ V* g
。。。。。。。。。。。
2 K/ v; c) M8 S/ I W$ Iledon5 O ?- [+ n+ m% L4 K9 x
...............
0 {5 h6 l1 X$ \) h3 l: _% ? ..............." O1 Y: f* W" t
。。。。。。。。。。。。
, }' j& W8 J, b2 b9 Z) D 。。。。。。。。。。。& ^7 Y. \( e. ]) S
adr r4,delay1+1
8 n- c& k8 H0 I, M4 _7 v bx r4
/ @0 c8 U" i9 ~" Q/ E
- |! c7 _+ l& u& j6 [2 L6 R t7 Aledoff8 [# }5 P- e* a' A
..............
5 H) R$ `/ H P ...............
# f- q- q& Z0 K9 k .............% [$ x. a" A2 U
.............
0 k9 |. H. {( R$ [! D$ q* b1 g# ]9 q .............
. T$ N/ E$ p" q4 ^4 X+ ? ..............4 h3 B0 \, x- i1 k f. O
! t; L/ w1 o4 c
AREA testasm,CODE,READONLY
8 h, W2 r1 S1 F CODE16. I% p# M( W7 w) l q, B& M
delay1
) W; b) U9 {6 z' \4 ^ ............
& |* `% T* ~9 y3 Z ?& x+ {( b ...........4 W4 a1 U* g0 Q7 H4 x
.............) z6 H" \# @# n1 G4 k v
ldr r1,=ledoff- M; h) \) b/ V; \
bx r1
; s; A) w1 H& P ........
0 k8 K1 o) _8 M' R1 F4 K .............( O Z5 }1 z# h# W& A0 b" `
.............+ E6 [! x4 P. r& y
END# w) E/ T( _# I- N
* q) E6 ]+ E' W2 l/ s: B) \ C; m9 d
关于delay1+1:
4 H4 a ^ i6 H& h; f7 [; lARM指令是字对齐(指令的地址后两位为[1:0]=0b00),Thumb是半字对齐(指令的地址后两位为[1:0]=0bx0,x为0或1)。指令的地址的最后一位必为0。
# N' x0 v, u6 x3 v4 ^0 {6 \因此bx(不管往ARM还是往Thumb跳转)的跳转指令必须保证指令地址的最后一位为0,上例中bx指令能自! z c+ D& x% A+ ^
动地将目标地址值置为r4的值和0xFFFFFFFE的与后的结果,就会使指令地址的最后一位必为0了。 ~& R6 Q8 K k& A; G
那么delay+1的这个1不就被0与掉了么,就没有什么作用了?其实,在执行bx指令时,它是首先判 B2 w2 w6 ^7 J( g
断指令地址的后一位为0或1(这样就知道要跳转的地方是ARM还是Thumb指令。0跳转arm,1跳转thumb。),然后再PC=r4 AND 0xFFFFFFFE。这样,当我们需要要跳转到Thumb指令处执行时,必须将指令地址的最后以为置1。
& |+ C3 P# A( s! u7 ]5 d# Y0 }而bx再跳转到ARM指令时就无需考虑这些了,就不用像上面的+1处理了。
* M6 V, W5 j, o. n- Z0 L$ F. g# w关于字对齐和半字对齐
什么叫对齐。如果一个数据是从偶地址开始的连续存储,那么它就是半字对齐,否则就是非半字对齐;半字对齐的特征是bit0=0,其他位为任意值。字对齐的特征是bit1=0,bit0=0 其他位为任意值。如果一个数据是以能被4 整除的地址开始的连续存储,那么它就是字对齐,否则就是非字对齐。举例说明四字节对齐: 对内存进行操作时,被访问的地址必须为4的倍数。如果分配到的地址不是4的倍数时,CPU实际访问的地址还是按照字对齐的方式来操作。也就是自动屏蔽bit1和bit0.
; d6 T9 c$ S+ G3 M$ U什么是对齐,以及为什么要对齐:. P. E- D8 j* i
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
对齐的作用和原因:
各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该int数据。显然在读取效率上下降很多。这也是空间和时间的博弈。1 ]: |$ M; M6 c0 W
对齐的实现
+ ]: p; Q7 ^, i( L+ ? 通常,写程序的时候,不需要考虑对齐问题。编译器会替我们选择适合目标平台的对齐策略。可以给编译器传递预编译指令而改变对指定数据的对齐方法。
char a char a
short b char c
char c short b
int d int d
ARM体系中对齐的使用:
3 C2 e% ~4 B: E5 |, o1.__align(num)7 i6 b) {# @8 i6 C% w1 I9 T
这个用于修改最高级别对象的字节边界。在汇编中使用LDRD或者STRD时
* `. f5 {* ?2 L8 d% M 就要用到此命令__align(8)进行修饰限制。来保证数据对象是相应对齐。
+ E& N- P- k3 \+ t# ` 这个修饰对象的命令最大是8个字节限制,可以让2字节的对象进行4字节9 ^5 s! F$ L% c2 Q$ o
对齐,但是不能让4字节的对象2字节对齐。
* ]6 I/ A( g8 h/ ^' ` __align是存储类修改,他只修饰最高级类型对象不能用于结构或者函数对象。
G# J- j( B5 h3 o% u8 B
) y! S4 B1 }1 J% G# Q; ^& U2.__packed " M6 t# O, m6 A4 j
__packed是进行一字节对齐
$ p% t5 h$ h/ S: \(1).不能对packed的对象进行对齐
5 A0 V, l( Z* H+ r! ](2).所有对象的读写访问都进行非对齐访问
* c1 J1 M5 J: ~/ ?# n- }(3).float及包含float的结构联合及未用__packed的对象将不能字节对齐+ X! G% m# |( B7 g- q
(4).__packed对局部整形变量无影响( ?$ L/ J. N! i) T R
(5).强制由unpacked对象向packed对象转化是未定义,整形指针可以合法定义为packed。3 f' }9 Z- \# t; W
__packed int* p; //__packed int 则没有意义. y" x5 q) y5 y* J
(6).对齐或非对齐读写访问带来问题3 m" y* r- h& A0 \, z
_packed struct STRUCT_TEST
: t. w2 F4 C) A- J( _9 _- P{, `3 m/ [" q7 [* U' Y& n, l
char a;
. p. M! y% r2 ^; M9 z' T; Q* K int b;% V; d7 y7 J( d1 T) h3 e
char c;
e! a$ Q- F! O2 z* g2 o+ O" b} ; //定义如下结构此时b的起始地址一定是不对齐的5 x$ h4 c8 y% j; X; W" k
//在栈中访问b可能有问题,因为栈上数据肯定是对齐访问[from CL]
8 y# b3 U$ Y6 ~( d+ o5 K//将下面变量定义成全局静态不在栈上
4 C" G0 m' U/ g9 P1 G: Astatic char* p;0 I2 ]* p( t3 Z$ L/ k
static struct STRUCT_TEST a;* H5 B3 z1 M. J6 N8 y7 R5 h
void Main(), K4 `- q" p' p: k7 S
{( z: L3 \' p9 D; o
__packed int* q; //此时定义成__packed来修饰当前q指向为非对齐的数据地址下面的访问则可以
p = (char*)&a;
' d% a( `3 l/ F+ `) b# \; ` q = (int*)(p+1);
* @' L; @4 R) j; Z8 z /*' Z6 _. ^# u% }/ t
*q = 0x87654321;0 |" U' I8 H( Q9 C
ldr r5,0x20001590 ; = #0x12345678
# }. v6 q8 D$ K8 }) e F" i g [0xe1a00005] mov r0,r5+ p- G" l3 @6 t$ N
[0xeb0000b0] bl __rt_uwrite4 //在此处调用一个写4byte的操作函数
* ]: e- ] B3 z' `& n7 g
7 U& J9 u0 p9 S* U4 I [0xe5c10000] strb r0,[r1,#0] //函数进行4次strb操作然后返回保证了数据正确的访问- R$ O$ i1 C$ P
[0xe1a02420] mov r2,r0,lsr #8% \! z# T' o" ?7 r5 U
[0xe5c12001] strb r2,[r1,#1]
" \) X6 c$ w9 d1 n [0xe1a02820] mov r2,r0,lsr #16
' t; Q) s% w2 X5 ~& @& w( u9 C [0xe5c12002] strb r2,[r1,#2]8 Q c, ?& I' q r( U) b8 [
[0xe1a02c20] mov r2,r0,lsr #24
o- o0 e4 R. h# i& | [0xe5c12003] strb r2,[r1,#3]; ^' t5 C! a) R; q* J- W, h: ^
[0xe1a0f00e] mov pc,r14
6 b9 U8 ]+ u+ j7 J( M1 T*/
/*
5 o. l3 L3 ^" f$ D& S //如果q没有加__packed修饰则汇编出来指令是这样直接会导致奇地址处访问失败4 E, k+ u' h" D) ~6 Y( V$ U- {0 G
[0xe59f2018] ldr r2,0x20001594 ; = #0x87654321! `6 O S$ u- j2 ?. ~1 ~
[0xe5812000] str r2,[r1,#0]7 A3 {$ S: ]0 p6 ?, p, [
*/
//这样可以很清楚的看到非对齐访问是如何产生错误的
; I8 z9 M' }+ s& U: P! Z% P! _* w, H //以及如何消除非对齐访问带来问题, S) H! K; v/ J* A2 |
//也可以看到非对齐访问和对齐访问的指令差异导致效率问题
% V: h9 p# e+ d9 z
" [5 R$ b: G* J; T先看一个小程序
7 R+ x$ G! g1 ?+ a* u#include<stdio.h>
) E* W9 T) K4 P. `% q8 a3 a4 U; [struct t1! U9 ?$ E5 b( ?* q& w/ o0 R
{
$ c: ?; e/ r0 [1 I" P4 R# T char a;
; }; `0 o2 Y9 I7 D. ~+ F5 H9 p# M" \ h int b;# M9 \1 V" O: M" C; Z
char c;% N' R% E2 L% x& A
};
struct t2
* y6 `" m: a2 Y$ y{9 ~7 J# g. A6 e2 D% ^
char a;' {- G( g( a( o3 L8 R1 Y+ P
char b;
7 H$ w6 E6 B$ g; {* } int c;3 p# ~- [, s7 x* |9 {
};
void main()
3 a+ @+ i" H2 k8 y1 C5 I7 u9 H0 g{
3 y3 _/ E1 u5 O/ r/ x' \ \ printf("%d/n",sizeof(struct t1));
7 [( o9 }: m; M1 N* I M1 W% n& j- H8 r' v printf("%d",sizeof(struct t2));( U/ N" J6 @: r' m7 ~/ K
}
按一般来说,结构体t1和t2的大小应该是一样的,但在很多编译器中,显示出的结果是不一样的,如我用的VS 2010,t1的大小为12字节,t2 为8字节,为什么会出现这样的结果呢?这其中有个东西就叫字对齐。& @& {" P9 [ ?% V- n& O8 s! h
据说很多公司在面试中都考过这个问题,有一年威盛也考过。。。7 \) K1 I# B0 e6 ?
字对齐,是昨天在看ARM的时候看到的,一般是4个字节为一个单位进行对齐,怎样对齐的呢?有点像平时我们写文档用的右对齐之类的,不一样的就是,句子可以换行写,但一个变量的存储,不是那种本身很长的,一般是不换行的,例如上面的例子,将空的内存看作
( ~# _% ?# d/ S- moooo
( M) s1 h" K) h( \; v* Koooo
) z4 Z+ S$ u8 i; R2 |oooo
* c+ C: y3 D9 ut1中为a分配内存,用一个字节,内存变为
9 U7 ]! @: u2 _7 m+ ixooo
$ ]- G W, V8 W) k! X. f Ooooo4 j$ _5 J. |/ q$ Q
oooo
+ z4 f, m% A S然后进行b的内存分配,在VS中,int占4个字节,但第一行只有3个字节空的了,所以就换行对齐,内存变为
7 s' r/ o- w) J- w3 P6 uxooo
" C* y, C0 T0 J+ s; P4 kxxxx) h$ U8 F) C" ~" {8 ]3 I2 a
oooo1 m, d f5 {% ~ E& H+ Z
最后为c分配,变成1 }7 H6 L$ W/ J. p1 O9 Z$ Y) c
xooo: f; y8 P" T( ]/ l
xxxx4 m! }( A3 L# _4 {. C
xooo) T/ O( X# a/ V, q
总共占用内存就是每行4个字节x3行=12字节。( V X* y: D( h5 j5 R: B& j
在t2中
) [, o( ]% f9 ~, S8 `内存同样为% U' ^% d( g; K
oooo3 P6 E* i: V5 Z1 ?" t1 o
oooo5 s: ~7 p: F. \; E N
oooo3 E$ U W+ H# y; e! @: v
为a分配时,同样是2 t) h+ d5 \) ]% U" o# }; t$ I
xooo
/ f0 v, z* j$ v3 e- c* w+ z为b分配时,由于第一行的长度能满足b所以b还是在第一行,内存为0 U8 P6 x; T9 b" z6 ?! |
xxoo
% m- ]- q$ q) u' X: X$ u/ aoooo2 T7 j% L" I: _: u
oooo8 k; i' S1 I i2 {) A
为c分配时,第一行已经不够,就换到第二行,
6 @( d! q: ?* u6 G) G' ]. Nxxoo _+ X* v, D2 i
xxxx# f Q# C& b6 {" B
oooo
( @* w' |$ Y; w1 o/ o4 j这样第三行就没用到,所占的内存就是每行4个字节x2行=8个字节。
这就是我所理解的字对齐,至于为什么,我想,应该是方便32位机操作吧,32位刚好就是4个字节,这样可以尽量使数据的读取用一次总线操作就完成,int是4个字节,32位,如果不用字对齐,读取一个int,可能第一次就只能读出高位那部分,然后通过移位将数据的高位移到寄存器的高位,再读出低位,再对第二次读取的高位屏蔽,再进行或操作,效率不高。同样,类似于char型和short型数据,读取了后还是会进行寄存器移位操作,这一点可以通过编译生成的汇编语句得到证实,这也就是为什么在某些系统,如嵌入式系统中,char的效率不如int高的原因了。
, f- R# J7 I; a0 v3 f5 h另外,半字对齐和字对齐差不多,只是它的单位是2个字节吧,因为有的嵌入式系统是16位的。
3 l9 _/ f+ _" V k- W# q( X
作者: SsaaM7 时间: 2020-10-14 16:07
ARM的BX指令
| 欢迎光临 EDA365电子论坛网 (https://bbs.eda365.com/) |
Powered by Discuz! X3.2 |