這個(gè)實(shí)驗(yàn)的目的是深入理解ARM和Thumb指令的特點(diǎn),了解編譯選項(xiàng)對(duì)代碼產(chǎn)生的影響。
配合課程
ARM處理器
實(shí)驗(yàn)?zāi)康?/h3>
- 深入理解ARM指令和Thumb指令的區(qū)別和編譯選項(xiàng);
- 深入理解某些特殊的ARM指令,理解如何編寫C代碼來得到這些指令;
- 深入理解ARM的BL指令和C函數(shù)的堆棧保護(hù);
- 深入理解如何實(shí)現(xiàn)C和匯編函數(shù)的互相調(diào)用。
實(shí)驗(yàn)器材
硬件
- 實(shí)驗(yàn)主板一塊(raspberryPi);
- 5V/1A電源一個(gè);
- microUSB線一根;
軟件
- 交叉編譯軟件(arm-linux-gcc)
操作方法和實(shí)驗(yàn)步驟
-
實(shí)驗(yàn)設(shè)備連接示意圖
連接示意圖
-
同一個(gè)程序,用arm指令和thumb指令編譯,得到的結(jié)果有什么不同
代碼如下:
//hello.c
#include <stdio.h>
int main(){
printf("Hello World\n");
return 0;
}
實(shí)驗(yàn)設(shè)備連接示意圖

同一個(gè)程序,用arm指令和thumb指令編譯,得到的結(jié)果有什么不同
代碼如下:
//hello.c
#include <stdio.h>
int main(){
printf("Hello World\n");
return 0;
}
用兩種不同的方式編譯:

得到的結(jié)果:

區(qū)別:
- thumb的指令地址一次只增加2,即每條指令只占16位。相對(duì)的,arm每個(gè)地址增加4,每條指令占位32位。
- 在 Thumb 狀態(tài)下,單寄存器加載和存儲(chǔ)指令只能訪問寄存器 R0~R7。
-
對(duì)于 ARM 指令,能否產(chǎn)生條件執(zhí)行的指令
可以的,C語言代碼:
//if.c
#include <stdio.h>
int main(){
int i;
i = 1;
if(i == 1)i++;
else i--;
return 0;
}
生成的ARM指令代碼<main>函數(shù)部分:
if_arm.S
其中可以看到bne指令。 -
設(shè)計(jì) C 的代碼場景,觀察是否產(chǎn)生了寄存器移位尋址
C語言代碼如下:
//shift.c
#include <stdio.h>
int lShift(int *i, int j){
return i[j << 2];
}
int main(){
int i[256];
int j = 18;
lShift(i, j);
return 0;
}
生成的ARM指令代碼主要部分(可以看到ldr指令):
shift_ARM.S -
設(shè)計(jì) C 的代碼場景,觀察一個(gè)復(fù)雜的 32 位數(shù)是如何裝載到寄存器的
C語言代碼如下:
//load32.c
#include <stdio.h>
int main(){
double i;
long j;
i = 204800001.1;
j = 2048000011;
return 0;
}
生成的ARM代碼如下:
Load32_ARM.S
可以看出,ARM是將32位數(shù)放到堆棧中,然后依次取出放入到寄存器當(dāng)中。 -
寫一個(gè) C 的多重函數(shù)調(diào)用的程序,觀察和分析: a. 調(diào)用時(shí)的返回地址在哪里? b. 傳入的參數(shù)在哪里? c. 本地變量的堆棧分配是如何做的? d. 寄存器是 caller 保存還是 callee 保存?是全體保存還是部分保存?
C代碼如下:
//mulFunc.c
#include <stdio.h>
int fun1(int i){
int j = i + 1;
return j;
}int fun2(int i, int j){ int x = i + j; x = fun1(x); return x; } int main(){ int a = 1, b = 2; a = fun2(a, b); return 0; }
得到的ARM匯編代碼如下:

a.調(diào)用時(shí)的返回地址放在lr寄存器當(dāng)中。
b.傳遞的參數(shù)放在了堆棧中。
c.被調(diào)函數(shù)首先將傳遞過來的堆棧指針 sp 拷貝到指針 fp 中,然后將本地變量從 r0 開始存放,當(dāng)存放到 r2 之后,其余的變量將會(huì)通過 r3 存放到堆棧中去,最后將 fp 中保存的指針拷貝回 sp,恢復(fù)被調(diào)前的堆棧情況,然后返回。
d.參數(shù)保存在caller,地址保存在callee,部分保存的
-
MLA 是帶累加的乘法,嘗試要如何寫 C 的表達(dá)式能編譯得到 MLA 指令
C代碼如下:
//mla.c
#include <stdio.h>
int mla(int i, int j, int k){
return i * j + k;
}int main(){ int i = 2, j = 20, k = 30; mla(i, j, k); return 0; }
編譯得到的ARM匯編文件(編譯時(shí)用-O1優(yōu)化編譯)

可以看到mla指令。
-
BIC是對(duì)某一個(gè)比特清零的指令,嘗試要如何寫 C 的表達(dá)式能編譯得到 BIC 指令
C代碼如下:
//bic.c
#include <stdio.h>
int bic(int i, int j){
return i&~j;
}int main(){ int i = 0x8828; int j = 20484; bic(i, j); return 0; }
匯編得到的ARM指令(要使用-O1編譯優(yōu)化)

可以很明顯的看到bic指令。
-
編寫一個(gè)匯編函數(shù),接受一個(gè)整數(shù)和一個(gè)指針做為輸入,指針?biāo)笐?yīng)為一個(gè)字符串,該匯編函數(shù)調(diào)用C語言的 printf()函數(shù)輸出這個(gè)字符串的前n個(gè)字符,n即為那個(gè)整數(shù)。在C語言寫的main()函數(shù)中調(diào)用并傳遞參數(shù)給這個(gè)匯編函數(shù)來得到輸出。
匯編函數(shù)代碼(cutprint.S):
.global cutprint
cutprint:
push {R5, R6, R7, lr}
MOV R5, R0
MOV R6, R1
MOV R7, #0
CMP R7, R6
BGE exit
begin:
LDR R0, =char
LDR R1, [R5, R7]
CMP R1, #0
BEQ exit
bl printf
ADD R7, R7, #1
CMP R7, R6
BLT begin
exit:
LDR R0, =newline
bl printf
MOV R0, R7
pop {R5, R6, R7, pc}.data char: .asciz "%c" newline: .asciz "\n"
C語言主函數(shù)代碼:
//cutprint.c
#include <stdio.h>
extern int cutprint(char *, int);
int main(){
int a;
char s[100];
scanf("%s %d", s, &a);
a = cutprint(s, a);
printf("Print %d character.\n", a);
return 0;
}
編譯后在樹莓派的執(zhí)行結(jié)果:



-
自選擴(kuò)展內(nèi)容1:編寫測試程序,測試ARM指令和Thumb指令的執(zhí)行效率
測試代碼:
//speed.c
#include <stdio.h>int fibo(int n){ if(n <= 1) return 1; return fibo(n - 2) + fibo(n - 1); } int main(){ int a; scanf("%d", &a); printf("%d\n", fibo(a)); return 0; }
在編譯時(shí),在普通編譯之外,還有-O2和-O3編譯優(yōu)化的編譯。


從測試結(jié)果中可以看到,arm的執(zhí)行速度要比thumb的執(zhí)行速度快一些。
-
自選擴(kuò)展內(nèi)容2:編寫測試程序,測試使用帶條件的ARM指令和不使用時(shí)的執(zhí)行效率。
含帶條件的ARM指令(useif.S):
.global add
add:
CMP R0, #0
ADDNE R0, R0, R1
MOV pc, lr
不含的ARM指令(noif.S):.global add add: CMP R0, #0 ADD R0, R0, R1 MOV pc, lr
測試代碼:
//test.c
#include <stdio.h>
extern int add(int, int);
int main(){
int a, b, i, times;
scanf("%d %d %d", &a, &b, ×);
for (i=0; i<times; i++)
a = add(a, b);
printf("%d\n", a);
return 0;
}
編譯選項(xiàng):


通過比較執(zhí)行結(jié)果可以發(fā)現(xiàn)兩個(gè)程序執(zhí)行的時(shí)間基本上沒有什么差別。
討論與心得
本次實(shí)驗(yàn)主要是對(duì)代碼的編寫和對(duì)ARM指令的了解,沒有涉及過多的硬件上的內(nèi)容。需要的硬件內(nèi)容(交叉編譯環(huán)境以及連接和文件傳遞)在上次實(shí)驗(yàn)已經(jīng)設(shè)計(jì),所以從總體上來說比上一次實(shí)驗(yàn)簡單。
同時(shí)本次使用和讓我了解到了ARM指令的一些性質(zhì),以及ARM指令和Thumb指令的區(qū)別。


