Model Me, Model World
Blayground
ARM指令集初见
1. 实验板连接图

- Acadia 一块,远程 Linux 服务器一台,Windows 笔记本一台
- 网线一根
- 5V 直流电源
- microUSB 线
2. 编译器调参
1. 生成了 Thumb 指令还是 ARM 指令
如何通过编译参数改变,相同的程序,ARM 和 Thumb 编译的结果有何不同,如指令本⾝和整体目标代码的大小等。
分别使用不同的编译选项编译 lzo 压缩算法代码,然后用objdump命令反汇编查看指令。
允许使用 Thumb 指令:
gcc -mtune=arm7tdmi -mthumb -o thumb.o lzo.c
objdump -d thumb.o允许 Thumb 指令编译结果:
00008464 <_do_compress>:
8464: e92d 01f0 stmdb sp!, {r4, r5, r6, r7, r8}
8468: b08f sub sp, #60 ; 0x3c
846a: af00 add r7, sp, #0
846c: 60f8 str r0, [r7, #12]
846e: 60b9 str r1, [r7, #8]
8470: 607a str r2, [r7, #4]
8472: 603b str r3, [r7, #0]
8474: 68fa ldr r2, [r7, #12]
8476: 68bb ldr r3, [r7, #8]
8478: 18d3 adds r3, r2, r3只使用 ARM 指令:
gcc -mtune=arm7tdmi -marm -o arm.o lzo.c
objdump -d arm.o只使用 ARM 指令编译结果:
00008464 <_do_compress>:
8464: e92d08f0 push {r4, r5, r6, r7, fp}
8468: e28db010 add fp, sp, #16
846c: e24dd03c sub sp, sp, #60 ; 0x3c
8470: e50b0040 str r0, [fp, #-64] ; 0x40
8474: e50b1044 str r1, [fp, #-68] ; 0x44
8478: e50b2048 str r2, [fp, #-72] ; 0x48
847c: e50b304c str r3, [fp, #-76] ; 0x4c
8480: e51b2040 ldr r2, [fp, #-64] ; 0x40
8484: e51b3044 ldr r3, [fp, #-68] ; 0x44
8488: e0823003 add r3, r2, r3(核心函数_do_compress部分编译结果)
对比:
a. 指令位数
Thumb 指令为 16bit 长,ARM 指令为 32bit 长。
b. 整体目标代码大小
允许 Thumb 指令时,_do_compress函数占用代码段0x8464~0x8937,共 1236 字节。
只使用 ARM 指令时,_do_compress函数占用代码段0x8464~0x8c14,共 1972 字节。
可见,使用了 Thumb 指令之后,目标代码整体大小更小。_do_compress函数的 Thumb 目标代码约比纯 ARM 目标代码小 37%。
c. 指令功能
Thumb 指令集不是一个完整的指令集,Thumb 指令只支持通用功能,在必要时需要借助于完善的 ARM 指令集。因此可以看到 Thumb 编译结果中含有 ARM 指令。
Thumb 指令在功能上受到一些限制。比如只能访问寄存器的一个子集,只有两个操作数,没有乘加指令及 64 位乘法指令,除了跳转指令 B 有条件执行功能外,其它指令均为无条件执行等。
ARM 指令功能完善,但生成二进制代码体积较大。
2. 对于 ARM 指令,能否产生条件执行的指令
指定使用 ARM 指令,并采用O1优化:
gcc -marm -O1 -o condition.o condition.c
objdump -d condition.o源代码如下:
int fun(int a, int b){
if(a>b) a++;
if(a==b) a--;
return a;
}
int main(void){
fun(10,10);
fun(2,3);
return 0;
}fun函数编译结果如下:
0000835c <fun>:
835c: e1500001 cmp r0, r1
8360: c2800001 addgt r0, r0, #1
8364: e1500001 cmp r0, r1
8368: 02400001 subeq r0, r0, #1
836c: e12fff1e bx lr可以看到,编译结果产生了addgt和subeq条件执行指令。
3. 设计 C 的代码场景,观察是否产生了寄存器移位寻址
指定使用 ARM 指令,并采用O1优化:
gcc -marm -O1 -o shift.o shift.c
objdump -d shift.o源代码如下:
int fun(int *a, int b){
a[b*2] = 10;
return a[b*4];
}
int main(void){
int a[10];
fun(a, 3);
return 0;
}fun函数编译结果如下:
0000835c <fun>:
835c: e3a0300a mov r3, #10
8360: e7803181 str r3, [r0, r1, lsl #3]
8364: e7900201 ldr r0, [r0, r1, lsl #4]
8368: e12fff1e bx lr编译结果中str r3, [r0, r1, lsl #3]和ldr r0, [r0, r1, lsl #4]使用了寄存器移位寻址。
4. 设计 C 的代码场景,观察⼀个复杂的 32 位数是如何装载到寄存器的
指定使用 ARM 指令,此时不使用O1优化:
gcc -marm -o load32.o load32.c
objdump -d load32.o源代码如下:
int fun(int a, int b){
return a+b;
}
int main(void){
fun(10, 0x71234567);
return 0;
}main函数编译结果如下:
0000838c <main>:
838c: e92d4800 push {fp, lr}
8390: e28db004 add fp, sp, #4
8394: e3a0000a mov r0, #10
8398: e3041567 movw r1, #17767 ; 0x4567
839c: e3471123 movt r1, #28963 ; 0x7123
83a0: ebffffed bl 835c <fun>
83a4: e3a03000 mov r3, #0
83a8: e1a00003 mov r0, r3
83ac: e8bd8800 pop {fp, pc}当需要把一个 32 位数装载到寄存器时,先装载低 16 位,然后装载高 16 位。其中movt指令执行的动作包括使寄存器左移 16 位,然后做或操作。
5. 写一个 C 的多重函数调⽤的程序,观察和分析
指定使用 ARM 指令,此时不使用O1优化:
gcc -marm -o call.o call.c
objdump -d call.o源代码如下:
int fun2(int a, int b, int c, int d, int e){
return a+b+c+d+e;
}
int fun1(int a, int b){
int c=11;
int d=12;
int e=13;
return fun2(a, b, c, d, e);
}
int main(void){
fun1(1, 2);
return 0;
}fun2函数编译结果如下:
0000835c <fun2>:
835c: e52db004 push {fp} ; (str fp, [sp, #-4]!)
8360: e28db000 add fp, sp, #0
8364: e24dd014 sub sp, sp, #20
8368: e50b0008 str r0, [fp, #-8]
836c: e50b100c str r1, [fp, #-12]
8370: e50b2010 str r2, [fp, #-16]
8374: e50b3014 str r3, [fp, #-20]
8378: e51b2008 ldr r2, [fp, #-8]
837c: e51b300c ldr r3, [fp, #-12]
8380: e0822003 add r2, r2, r3
8384: e51b3010 ldr r3, [fp, #-16]
8388: e0822003 add r2, r2, r3
838c: e51b3014 ldr r3, [fp, #-20]
8390: e0822003 add r2, r2, r3
8394: e59b3004 ldr r3, [fp, #4]
8398: e0823003 add r3, r2, r3
839c: e1a00003 mov r0, r3
83a0: e28bd000 add sp, fp, #0
83a4: e8bd0800 pop {fp}
83a8: e12fff1e bx lrfun1函数编译结果如下:
000083ac <fun1>:
83ac: e92d4800 push {fp, lr}
83b0: e28db004 add fp, sp, #4
83b4: e24dd020 sub sp, sp, #32
83b8: e50b0018 str r0, [fp, #-24]
83bc: e50b101c str r1, [fp, #-28]
83c0: e3a0300b mov r3, #11
83c4: e50b3010 str r3, [fp, #-16]
83c8: e3a0300c mov r3, #12
83cc: e50b300c str r3, [fp, #-12]
83d0: e3a0300d mov r3, #13
83d4: e50b3008 str r3, [fp, #-8]
83d8: e51b3008 ldr r3, [fp, #-8]
83dc: e58d3000 str r3, [sp]
83e0: e51b0018 ldr r0, [fp, #-24]
83e4: e51b101c ldr r1, [fp, #-28]
83e8: e51b2010 ldr r2, [fp, #-16]
83ec: e51b300c ldr r3, [fp, #-12]
83f0: ebffffd9 bl 835c <fun2>
83f4: e1a03000 mov r3, r0
83f8: e1a00003 mov r0, r3
83fc: e24bd004 sub sp, fp, #4
8400: e8bd8800 pop {fp, pc}main函数编译结果如下:
00008404 <main>:
8404: e92d4800 push {fp, lr}
8408: e28db004 add fp, sp, #4
840c: e3a00001 mov r0, #1
8410: e3a01002 mov r1, #2
8414: ebffffe4 bl 83ac <fun1>
8418: e3a03000 mov r3, #0
841c: e1a00003 mov r0, r3
8420: e8bd8800 pop {fp, pc}a. 调用时的返回地址在哪?
返回地址存储在栈中。调用函数前,push {fp, lr}指令将帧指针寄存器和连接寄存器都压入了栈中。
b. 传递的参数在哪里?
当参数不多于 4 个时,参数通过r0、r1、r2、r3寄存器传送。当参数多于 4 个时,超过的参数使用栈传送。
比如main函数中,调用fun1时传了两个参数,置于r0、r1中:
840c: e3a00001 mov r0, #1
8410: e3a01002 mov r1, #2
8414: ebffffe4 bl 83ac <fun1>函数fun1中,调用fun2传了 5 个参数,其中一个置于栈中:
83dc: e58d3000 str r3, [sp]
83e0: e51b0018 ldr r0, [fp, #-24]
83e4: e51b101c ldr r1, [fp, #-28]
83e8: e51b2010 ldr r2, [fp, #-16]
83ec: e51b300c ldr r3, [fp, #-12]
83f0: ebffffd9 bl 835c <fun2>c. 本地变量的堆栈分配是如何做的?
本地变量是存在当前函数的帧中的,使用fp帧指针寄存器来管理。
比如fun1中的c、d、e三个变量,分别存在fp-16、fp-12、fp-8的地址内。
83c0: e3a0300b mov r3, #11
83c4: e50b3010 str r3, [fp, #-16]
83c8: e3a0300c mov r3, #12
83cc: e50b300c str r3, [fp, #-12]
83d0: e3a0300d mov r3, #13
83d4: e50b3008 str r3, [fp, #-8]d. 寄存器是 caller 保存还是 callee 保存?是全体保存还是部分保存?
callee 保存。部分保存,只保存需要用的寄存器原值。
比如fun1在设置了sp和fp寄存器后,把r0、r1保存。
83b8: e50b0018 str r0, [fp, #-24]
83bc: e50b101c str r1, [fp, #-28]6. MLA 是带累加的乘法,尝试要如何写 C 的表达式能编译得到 MLA 指令
指定使用 ARM 指令,并采用O1优化:
gcc -marm -O1 -o mla.o mla.c
objdump -d mla.o源代码如下:
int fun1(int a, int b, int c){
return a*b+c;
}
int main(void){
fun1(1, 2, 3);
return 0;
}fun1函数编译结果如下:
0000835c <fun1>:
835c: e0202091 mla r0, r1, r0, r2
8360: e12fff1e bx lr编译结果中mla r0, r1, r0, r2是 MLA 指令。
####7.BIC 是对某一个比特清零的指令,尝试要如何写 C 的表达式能编译得到 BIC 指令
指定使用 ARM 指令,此时不使用O1优化:
gcc -marm -o bic.o bic.c
objdump -d bic.oint fun1(int a, int b, int c){
return a&0xaaaa;
}
int main(void){
fun1(1, 2, 3);
return 0;
}00008390 <fun1>:
8390: e3c00c55 bic r0, r0, #21760 ; 0x5500
8394: e3c00055 bic r0, r0, #85 ; 0x55
8398: e1a00800 lsl r0, r0, #16
839c: e1a00820 lsr r0, r0, #16
83a0: e12fff1e bx lr